概要

項目内容
案件IDMAS-078
カテゴリパイプライン / 資金繰り予測
PhasePhase 2
優先度P1 (★★)
所要時間2-3時間
対象ファイル600_report/606_datamart_daily_cf.jsdmBuildPlanCashflow_ への合流フック追加)、100_config/101_sys_config.js03_sys_params にパラメータ追加)、docs/spec/spec_daily_cf.md(仕様追記)
前提案件MAS-008(Cash Runway・84 タブ構造を前提)、406_rpa_pipeline.js(パイプライン→INV 起票の既存ロジック)
後続案件84 タブ UI 拡張(ソース別フィルタ/シナリオ比較)

目的

84_cf_daily_plan(日次 CF 計画)は現状 32_wrk_invoice の確定請求のみを入金予測ソースとしており、受注前のパイプライン案件(見込み売上)が CF 予測に反映されていない。この結果、資金繰り予測は「既に確定している案件」しか見えず、営業段階の見込み入金を加味した経営判断ができていない。

本仕様では 21_bud_pipeline の見込み売上を確度(ヨミ)に基づきフィルタリング・重み付けしたうえで、仮想の CF 入金イベントとして生成し、既存の INV 由来 cashEvents 配列に合流させる。これにより 84 タブは「確定分 + 見込み分」のハイブリッド予測となり、MAS-008 のランウェイ指標もより現実的な値を返す。

MAS-078 解決の中核課題

  1. 二重計上の防止(最重要): パイプライン案件が既に受注・請求段階に進み 32_wrk_invoice にも起票されている場合、何もガードしないと入金見込みが 2 回計上される。インメモリの Set を使った排他ロジックを必ず組み込む
  2. 確度加重のパラメータ化: 「確度 60% 以上を 100% 計上」か「金額×確度% の期待値」か、運用で切替可能に
  3. 実績モード(isActualOnly)の尊重: 過去月スナップショット表示時はパイプライン合流をスキップ

現在のコード

1. 日次 CF 生成のエントリ(600_report/606_datamart_daily_cf.js

ファイル内の 2 関数:

関数役割出力先タブ
dmBuildDailyCashflow_ (L59-192)実績日次 CF(33_wrk_bank 消込STL のみ)81_cf_daily_actual 等
dmBuildPlanCashflow_ (L199-375)計画日次 CF(32_wrk_invoice 由来 + 33_wrk_bank 実績ハイブリッド)84_cf_daily_plan

本仕様の改修対象は dmBuildPlanCashflow_ のみ。cashEvents 配列(L249 以降)へパイプライン由来のイベントを追加する。

2. cashEvents 配列の構造(L293-301 既存定義)

cashEvents.push({
  date: dateStr,           // 'YYYY-MM-DD'(決済予定日)
  src: approvedOnly ? '計上' : '計画',
  refId: invId,            // 'INV_YYYYMMDD_NNNN'
  acc: pm,                 // 決済手段
  memo: memo + ' [INV:' + invId + ']',
  inAmt: isIn ? cfAmt : 0,
  outAmt: isIn ? 0 : cfAmt
});

パイプライン由来イベントも同じ構造で push すれば、以降のソート・残高計算ロジック(L335-349)はそのまま流用できる。既存の filterValues / filterWithRecalcTotal 等のフィルタ設計には影響しない(84 タブは日次グリッドで月次マート共通ロジックとは独立)。

3. 21_bud_pipeline の列構成(100_config/101_sys_config.js:662

["有効フラグ","管理ID","PJ・案件名","契約形態","売上科目","確度(ヨミ)","計上開始年月",
 "スポット売上・初期費用","継続月額(MRR)","継続月数","取引先名","決済手段","CF計上",
 "入金ラグ(月)","入金日","休日調整","組織名","起票ターゲット月","最終起票年月日","備考"]

本仕様でマッチング・CF 生成に使う列:

用途
有効フラグ有効行のみ対象(CLAUDE.md 規約)
管理ID(PIP_NNNN除外 Set のキー、rawEvent の refId
PJ・案件名摘要生成
確度(ヨミ)フィルタ/加重
計上開始年月入金予定日算出の起点
スポット売上・初期費用初月単発入金
継続月額(MRR)N ヶ月分連続入金
継続月数MRR の展開月数
取引先名摘要生成
決済手段acc フィールド
CF計上true のみ対象(false ならキャッシュに載せない運用)
入金ラグ(月)/入金日入金予定日算出

4. パイプライン→INV 起票の既存ロジック(400_domain/406_rpa_pipeline.js

generatePipelineInvoices(targetYm) が実行されると:

  • 31_wrk_order を起票 → 参照元ID = PIP_XXXXpipeMgrId
  • 32_wrk_invoice を起票 → 親発注ID(ORD) = ORD_...、摘要は 【RPA:PIPE】{ym} {pjName} スポット または 【RPA:PIPE】{ym} {pjName} MRR N/Mヶ月目

本仕様での二重計上防止のキー: 31_wrk_order.参照元IDPIP_ で始まる行 → その参照元 ID(= pipeline の管理 ID)を Set に登録し、21_bud_pipeline.管理ID との一致で対象除外。

5. 03_sys_params パラメータ基盤

Utils.getSystemParam(key, defaultValue) 経由で取得。本仕様で 3 パラメータを追加:

キーDefault用途
PIPE_CF_CONFIDENCE_THRESHOLD0.60確度閾値(閾値モードで >= 採用)
PIPE_CF_WEIGHT_MODEthresholdthreshold(閾値以上で 100% 計上) / weighted(期待値 = 金額 × 確度)
PIPE_CF_TAX_RATE0.10消費税率(税抜 → 税込変換)

修正方針

全体像: dmBuildPlanCashflow_ の既存 INV ループ終了後、cashEvents.sort の直前に新規ヘルパー関数 dmAppendPipelineCashflow_(cashEvents, ctx, opts) の呼出を挿入する。新規ヘルパーは独立関数で、isActualOnly モード時はスキップする。

Step 1: 二重計上防止の除外 Set 構築

既に INV 起票済のパイプライン案件を Set<string> で識別する。キー形式: pipeMgrId(= PIP_NNNN 形式)。

実装手順

  1. 31_wrk_order から 参照元ID 列を読み取り、PIP_ で始まる全 ID を invoicedPipeIdSet に登録
  2. 追加の保険として 32_wrk_invoice の摘要列を走査し、【RPA:PIPE】 を含む行の親 ORD → その 参照元ID を invoicedPipeIdSet に加算(ORD が未設定のケースへの保険)
  3. パイプラインループ中、invoicedPipeIdSet.has(pipeMgrId) が真なら continue(そのパイプラインの全月分を CF から除外)

重複防止ロックの原則

  • 1 件でも紐づく INV があればパイプライン全体を CF から除外(仕様書人間検討事項 4・部分起票問題への対応)
  • 同一バッチ内でも Set 参照で O(1) 判定、ロック処理と同等の排他保証を実現
  • 失敗パターン #16(matched フラグ欠如による二重消費)を踏襲し、メモリ上フラグを必ず保持

Step 2: パイプライン→仮想 CF イベント展開

確度フィルタ/加重

var threshold = Number(Utils.getSystemParam('PIPE_CF_CONFIDENCE_THRESHOLD', 0.60)) || 0.60;
var mode      = String(Utils.getSystemParam('PIPE_CF_WEIGHT_MODE', 'threshold')).trim();

// 確度(ヨミ)を 0-1 に正規化。文字列 "60%" や "0.6" を受容。空欄・不正値は 0
function normalizeConfidence_(raw) {
  if (raw === null || raw === undefined || raw === '') return 0;
  var s = String(raw).trim();
  if (s.endsWith('%')) s = s.substring(0, s.length - 1);
  var n = Number(s);
  if (!isFinite(n)) return 0;
  if (n > 1) n = n / 100;       // "60" → 0.6
  if (n < 0) return 0;
  if (n > 1) return 1;           // 上限クランプ
  return n;
}

// mode === 'threshold': 確度 >= 閾値 なら倍率 1.0、未満なら 0.0(除外)
// mode === 'weighted':  倍率 = 確度そのもの(0-1)
function computeWeight_(confidence) {
  if (mode === 'weighted') return confidence;
  return (confidence >= threshold) ? 1.0 : 0.0;
}

月次展開ロジック

1 パイプライン行 → N 件の CF イベント(スポット 1 件 + MRR × 継続月数)

// 計上開始年月 (YYYY-MM) から展開
var startYm = Utils.parseDateToYm(pipeRow['計上開始年月']);
if (!startYm) continue;  // エッジケース 1: 空欄 → 除外

var duration = Math.max(1, Math.min(120, Number(pipeRow['継続月数']) || 1));
var spot     = Number(pipeRow['スポット売上・初期費用']) || 0;
var mrr      = Number(pipeRow['継続月額(MRR)']) || 0;

// 各月の入金予定日算出: 計上月 + 入金ラグ月、日付は入金日(未設定なら月末)
var paymentDay = Number(pipeRow['入金日']) || 0;  // 0 = 月末
var lagMonths  = Number(pipeRow['入金ラグ(月)']) || 0;

for (var m = 0; m < duration; m++) {
  var billYm   = Utils.addMonths(startYm, m);
  var dueYm    = Utils.addMonths(billYm, lagMonths);
  var dueDate  = dmResolvePipelinePaymentDate_(dueYm, paymentDay);  // YYYY-MM-DD

  // スポットは初月のみ、MRR は毎月
  var amountExcl = (m === 0 ? spot : 0) + mrr;
  if (amountExcl <= 0) continue;

  var weight = computeWeight_(confidence);
  if (weight <= 0) continue;  // 閾値未満・確度不正 → 除外

  var taxRate  = Number(Utils.getSystemParam('PIPE_CF_TAX_RATE', 0.10)) || 0.10;
  var amountIncl = Math.round(amountExcl * weight * (1 + taxRate));

  cashEvents.push({
    date:   dueDate,
    src:    '見込',  // S-06 追加ソース(確定=計画/計上/実績、見込=S-06 のみ)
    refId:  pipeMgrId,
    acc:    String(pipeRow['決済手段'] || ''),
    memo:   '【S-06 見込】' + pjName + ' ' + (m === 0 ? 'スポット+MRR1' : 'MRR' + (m + 1)) + ' [確度=' + Math.round(confidence * 100) + '% × ' + (mode === 'weighted' ? 'weighted' : 'threshold') + '] [PIP:' + pipeMgrId + ']',
    inAmt:  amountIncl,   // パイプラインは常に収入(売上)
    outAmt: 0
  });
}

入金予定日算出ヘルパー

function dmResolvePipelinePaymentDate_(ym, paymentDay) {
  // ym = 'YYYY-MM'。paymentDay = 0 or 未指定 → 月末。正数なら指定日
  var parts = ym.split('-');
  var y = Number(parts[0]), mo = Number(parts[1]);
  if (!y || !mo) return '';
  if (paymentDay <= 0) {
    var lastDay = new Date(y, mo, 0).getDate();  // 翌月 0 日 = 当月末日
    return y + '-' + String(mo).padStart(2,'0') + '-' + String(lastDay).padStart(2,'0');
  }
  // 月末を超える指定日(例: 2月31日)は月末へクランプ
  var lastDay2 = new Date(y, mo, 0).getDate();
  var d = Math.min(paymentDay, lastDay2);
  return y + '-' + String(mo).padStart(2,'0') + '-' + String(d).padStart(2,'0');
}

Step 3: cashEvents への合流とソース区分の可視化

dmBuildPlanCashflow_ への呼出挿入(L302 直後、L335 sort の直前)

// 既存の INV ループ終了直後、実績込みロジック(L304-)の後、ソート(L335-)の前
dmAppendPipelineCashflow_(cashEvents, ctx, ss, opts);

新規ヘルパー関数のシグネチャ

/**
 * S-06: 21_bud_pipeline から仮想の CF 入金イベントを cashEvents に合流させる
 * - isActualOnly=true ならスキップ
 * - 既に INV 起票済のパイプライン(31_wrk_order.参照元ID='PIP_...' が存在)は除外
 * - 確度閾値 or 期待値モードでフィルタ/加重
 * @param {Array} cashEvents - L249 の既存配列(参照渡しで push)
 * @param {Object} ctx - 既存 ctx
 * @param {GoogleAppsScript.Spreadsheet.Spreadsheet} ss
 * @param {Object} opts - { actualsOnly: boolean }(既存 opts 流用)
 */
function dmAppendPipelineCashflow_(cashEvents, ctx, ss, opts) { /* ... */ }

ソース区分の可視化(Human-in-the-Loop)

84 タブのヘッダー行の「ソース」列(既存 B 列)に以下の値を入れ分け:

  • 既存: 実績 / 計上 / 計画approvedOnly の分岐)
  • 追加: 見込(MAS-078 パイプライン由来を示す専用文字列)

さらに、L179-184 の既存「残高 < 0 赤ハイライト」条件付き書式に加え、B 列が 見込 の行は背景薄青 にするルールを追加(608_datamart_render.js の該当箇所 or dmBuildPlanCashflow_ 末尾)で視覚的に区別。

isActualOnly モード時のスキップ

function dmAppendPipelineCashflow_(cashEvents, ctx, ss, opts) {
  opts = opts || {};
  if (opts.actualsOnly) {
    Utils.logInfo('dmAppendPipelineCashflow_', 'isActualOnly=true のためスキップ');
    return;
  }
  // ... 以下本処理
}

影響範囲

変更対象変更内容変更量
600_report/606_datamart_daily_cf.jsdmAppendPipelineCashflow_ 新規追加 + dmBuildPlanCashflow_ 末尾で呼出+150 行
100_config/101_sys_config.js03_sys_params 初期データに 3 キー追加+3 行
docs/spec/spec_daily_cf.mdMAS-078 セクション追記+25 行
  • 既存動作への影響なし: dmBuildPlanCashflow_ の既存 INV ループ・実績 STL ロジック・ソート処理には touch しない
  • DTO / DDL への影響なし: 21_bud_pipeline / 32_wrk_invoice / 33_wrk_bank の列構成不変
  • 81_cf_daily_actual(実績専用)への影響なし: dmBuildDailyCashflow_ は touch しない
  • MAS-008(Cash Runway)との関係: MAS-008 のバーンレートは 営業 CF(cfOp) を入力としており、日次 84 タブ由来ではない。本改修は MAS-008 に直接の波及なし(ただし月次集計側で日次を参照する場合は間接波及があり得るため実装時確認)

注意事項

  1. 二重計上防止 Set は必須: invoicedPipeIdSet を漏れなく構築すること。31_wrk_order.参照元ID32_wrk_invoice.摘要(【RPA:PIPE】) の両経路を見ること(失敗パターン #16 継承)
  2. 1 件でも紐付く INV があれば全月除外: パイプラインの一部月のみが起票済のケース(部分起票)では全体を CF 予測から除外する保守的方針。残額を別途カウントしようとすると粒度管理が複雑化し、結果として二重計上が起きやすい
  3. isActualOnly モードでは必ずスキップ: 過去月スナップショット・監査用途で呼ばれるため、見込みを混入させない(失敗パターン #1 の非加算指標と同じ慎重運用)
  4. 確度 0-1 正規化: 21_bud_pipeline の「確度(ヨミ)」列は実データで "60%"0.6 の混在が想定される。normalizeConfidence_ で両対応。空欄は 0 で除外
  5. 閾値モード vs 期待値モード: 03_sys_paramsPIPE_CF_WEIGHT_MODE で切替。運用ポリシー(保守的 or 積極的)で選択。初期値は threshold(確度 60% 以上で 100% 計上)
  6. 消費税の簡易扱い: パイプライン税抜 × (1 + PIPE_CF_TAX_RATE) で税込化。非課税・免税案件の考慮は本仕様外(将来 MAS-089(消費税税抜方式)の仕組みと統合検討)
  7. 入金予定日のフォールバック: 入金ラグ(月)未設定 → 0、入金日未設定 → 月末(エッジケース 1)
  8. CF計上 列 = FALSE の行はスキップ: 21_bud_pipeline.CF計上 が false の場合、当該パイプラインは CF 予測の対象外(既存業務ルール)
  9. 列インデックスのハードコード禁止: h.indexOf('管理ID') 等で動的取得、-1 なら即 throw(失敗パターン #18 系)
  10. Utils.parseDateToYm / addMonths / parseDateToYmd を必ず使用: 独自日付パース禁止(失敗パターン #17 継承)
  11. 過去滞留案件の扱い: 計上開始年月が過去で、INV 未起票かつ入金予定日も過去のパイプラインは当月スライドせずそのまま過去日付で計上(84 タブは過去日付の見込みも表示し、営業の放置を可視化する設計意図)。エッジケース 3 で明示
  12. パフォーマンス: 31_wrk_order / 32_wrk_invoice の全行走査は 1 回のみ(Set 構築)。パイプラインループは O(N × duration) なので、継続 120 ヶ月 × 100 件のような極端ケースでも GAS 6 分制限には余裕

エッジケース

#条件処理理由
1日付フォールバック: 入金予定日が空欄 / 計上開始年月のみ存在発生予定月 + 入金ラグ(月)月末を自動算出(入金日=0 扱い)。入金日が正数なら当日入金日未設定の運用データを安全に展開。失敗パターン #17(Date 正規化)継承
2確度の不正値: 確度(ヨミ) が空欄 / 文字列 / NaNnormalizeConfidence_0 として扱い、computeWeight_ が 0 を返し除外CF 予測に不確実データが紛れるのを防ぐ(threshold モード・weighted モード共通)
3過去滞留案件: 計上開始年月が過去で、INV 未起票・入金予定日も過去当該日付のまま計上(当月スライドしない)過去日付の見込み残りを可視化し、営業の放置を検知(84 タブの日次グリッド上で古い日付に見込みが溜まっている状態が検出可能)
4部分起票: パイプラインの一部月のみ INV 起票済パイプライン全体を CF から除外粒度管理の複雑化を避け、二重計上の取りこぼしを防ぐ保守的方針
5CF計上=false の行候補から除外21_bud_pipeline の既存運用ルール(建付け予定案件等)
6スポット=0 かつ MRR=0展開スキップ(月 0 円になるため)ノイズ削減
7継続月数 = 0 or 未設定デフォルト 1 ヶ月 として初月のみ展開スポット単発案件を安全に表示
8**継続月数 > 120(10 年超)**の異常値上限 120 にクランプGAS 実行時間保護
9閾値モード × 確度 < 閾値倍率 0 → 除外運用ポリシー通り
10期待値モード × 確度 100%倍率 1.0 → 全額計上正常挙動
11入金日 = 31 かつ 2 月等の存在しない日付当月末日にクランプMath.min(paymentDay, lastDay)Date コンストラクタの自動調整による想定外挙動を回避
12パイプライン管理 ID が PIP_ 以外(手動入力等)除外 Set のキー形式不一致で二重計上リスク → 警告ログ Utils.logInfo 出力手動データの混入を運用で検知可能
13isActualOnly=true モード合流処理全体をスキップ、既存 INV+STL のみで出力実績スナップショット監査用途の純粋性を保つ
14消費税非課税・免税の案件本仕様では一律 10% 課税として扱う将来 MAS-089(税抜方式切替)と統合検討(人間検討事項 3)
151 ファイル複数シナリオ比較(将来): Base/Best/Worst で異なる確度本仕様ではシステム全体で 1 つの閾値/モード§4.8.2(シナリオ比較)実装後に拡張

実データ検証(事前確認項目)

確認項目確認方法確認結果(Phase 1 で調査済)
21_bud_pipeline の HEADERS100_config/101_sys_config.js:662✅「確度(ヨミ)」「計上開始年月」「スポット売上・初期費用」「継続月額(MRR)」「継続月数」「入金ラグ(月)」「入金日」「CF計上」すべて存在
31_wrk_order.参照元ID の命名規則400_domain/406_rpa_pipeline.js:149PIP_NNNN 形式で書き込まれる
32_wrk_invoice.摘要【RPA:PIPE】 プレフィックス406_rpa_pipeline.js:137, 169, 209✅ スポット/MRR 両経路で付与される
dmBuildPlanCashflow_cashEvents 配列構造606_datamart_daily_cf.js:293-301✅ 本仕様の追加 push もこの構造を踏襲
cashEvents.sort のキーL336 a.date.localeCompare(b.date)✅ 文字列 YYYY-MM-DD 比較、本仕様で生成する dueDate と整合
Utils.addMonths の挙動000_infra/004_utils.js:127'YYYY-MM' + N'YYYY-MM' を返す既存ヘルパー
Utils.parseDateToYm / parseDateToYmd の存在004_utils.js:108✅ 既存
03_sys_params の初期データ追加パターンF-08 Cash Runway で既に 2 キー追加済✅ 同パターンで 3 キー追加
608_datamart_render.js の条件付き書式ルール追加位置MAS-008 で追加済✅ 同箇所に「src=見込 の行背景色」ルールを並べる

実行前に運用で追加確認すべき項目:

  • 21_bud_pipeline の実データで「確度(ヨミ)」列が 0.60 / "60%" / 60 のどれで入力されているか
  • CF計上 列の運用実態(TRUE/FALSE の比率、運用者への認知度)
  • 既存の INV 由来 cashEvents 件数と、追加される見込み件数の比率(UI 負荷見積り)

プロダクトポリシー

ソース区分の可視化(Human-in-the-Loop)

  • ソース列(B 列)に 見込 の値を明示。既存 実績 / 計上 / 計画 と並列配置
  • 条件付き書式で 見込 行の背景を薄青 (#E3F2FD) にし、確定分との視認区別を確保
  • 摘要列に 【S-06 見込】{pjName} 確度=X% × threshold/weighted を明示、監査時にパラメータ・経路を追跡可能

パラメータ化の運用ガイド

パラメータ運用推奨値根拠
PIPE_CF_CONFIDENCE_THRESHOLD0.60一般的な営業確度のヨミで 60% 以上が「受注見込み」。VC 系は 0.50 も可
PIPE_CF_WEIGHT_MODEthreshold(初期値)保守的運用を優先。将来的に weighted へ切替可能
PIPE_CF_TAX_RATE0.10国内標準税率。軽減税率・免税案件は個別対応(将来拡張)

整合性

  • MAS-085(78 vs 92 整合性チェック)と同様、本機能の加わった 84 タブは確定分 = 92_fs_pl の将来月分と一致することを期待。ただし見込み分は 92 タブに載らないため、ソース列で判別可能にすることで差分を明示

ログ

Utils.logInfo('dmAppendPipelineCashflow_', 'N 件の見込みイベントを合流・X 件を除外(INV 起票済)') でサマリログを残し、後日の監査を可能にする。

関連ドキュメント

仕様書関連箇所
spec/spec_daily_cf.md日次 CF(84 タブ)の業務仕様。本仕様追記先
dev_mas-008_cash_runway.md同じ 606/605 系マートの拡張。03_sys_params パラメータ追加パターンの先行例
dev_mas-162_bank_combo_match.mdmatched フラグ・Set による二重消費防止の先行事例(失敗パターン #16)
dev_mas-089_consumption_tax_exclusive_method.md将来の税抜方式切替時の統合ポイント
CLAUDE.md84_cf_daily_plan は未決済残高ベース + 実績STLハイブリッドの運用規約。Human-in-the-Loop・有効フラグ判定
failure_patterns.md#13-#17(マッチング設計)、#18-#20(Read 裏取り)
TODO_future.mdMAS-078 案件定義

人間が検討すべき事項

#項目詳細
1確度加重の閾値設定(TODO_future.md 転記)PIPE_CF_CONFIDENCE_THRESHOLD の初期値は 0.60。業種(SaaS は 0.50・SI は 0.70 等)や営業精度で調整。03_sys_params でパラメータ化するため運用で変更可能
2閾値モード vs 期待値モードの選択本仕様では初期値 threshold。ポートフォリオ案件が多い場合は weighted(期待値)の方が現実的。運用 3 ヶ月後に見直しレビュー
3消費税率のハードコード vs マスタ化本仕様では PIPE_CF_TAX_RATE=0.10 固定。非課税・免税案件がある場合、21_bud_pipeline に「税区分」列を追加する拡張が必要。MAS-089(税抜方式)と統合検討
4部分起票除外の粒度「1 件でも INV があれば全月除外」は保守的。逆に「INV 起票済月のみ除外、未起票月は見込み計上」とすると粒度管理が必要になるが精度向上。初期は保守的方針でスタート
5過去滞留案件の可視化過去日付の見込みをそのまま表示する挙動(エッジケース 3)は「放置営業案件の検知」に有用だが、経理視点では煩雑に見える可能性あり。UI で「過去見込み行のみ非表示」フィルタ追加を検討
6シナリオ別ランウェイ(MAS-008 との連携)Best/Base/Worst で異なる確度閾値を適用したランウェイ比較。§4.8.2 シナリオ比較実装後に本仕様を拡張
7CF計上 列の運用啓発パイプライン担当者が CF計上=false を積極的に使うかは未知。運用開始後にデフォルト値を true vs false どちらにすべきかレビュー
8複数口座対応現行の cashEvents.acc は決済手段の文字列のみ。口座別残高推移(メイン口座 vs ファクタリング専用口座等)は本仕様範囲外
9確度更新時の再計算頻度確度が変わると CF 予測が動く。84 タブは日次 CF 生成時に毎回再計算されるため、営業が確度を変更してマート更新すれば即反映される既存挙動でカバー

実装プロンプト(Claude Code 用)

【タイムアウト回避・実行原則(v1.7)】
1. Phase 1(設計)では拡張思考フル活用・Read で裏取り。Phase 2(清書)の各 Step は最小限思考で書き下し。
2. 「〜作成します」等の text のみで tool_use なしに turn 終了しない。
3. 実装は 骨格 Write → 追記 Edit/Bash を分割実行。1 回あたり ~300 行以内。
4. 各 Step で書く内容を事前に洗い出してから tool_use へ進む。

あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-078「84タブにパイプライン売上を合流」を実装してください。

## 実行前タスク
以下のファイルを読み込み、既存パターンを把握してください:
1. `docs/dev/dev_mas-078_pipeline_cf_integration.md` — 本仕様書
2. `600_report/606_datamart_daily_cf.js` — 特に以下:
   - `dmBuildPlanCashflow_`(L199-375)全体
   - L249 以降の `cashEvents` 構築ロジック
   - L293-301 の push 構造(dateStr / src / refId / acc / memo / inAmt / outAmt)
   - L335-349 のソート・残高計算
3. `400_domain/406_rpa_pipeline.js` — `31_wrk_order.参照元ID='PIP_...'` の書込箇所(L149)、`32_wrk_invoice.摘要='【RPA:PIPE】...'` の箇所(L137, L169, L209)
4. `100_config/101_sys_config.js:662` — `BUD_PIPE` の HEADERS 定義
5. `000_infra/004_utils.js` — `parseDateToYm`(L108)、`addMonths`(L127)、`parseDateToYmd`、`getSystemParam`
6. `CLAUDE.md` — 列参照ヘッダー名ベース規約、有効フラグ判定、84_cf_daily_plan のハイブリッド運用
7. `docs/_internal/failure_patterns.md` — #16(matched フラグ)、#17(Date 正規化)、#18-#20(Read 裏取り)
8. `docs/dev/dev_mas-008_cash_runway.md` — 直近の 03_sys_params 追加パターン
9. `docs/dev/dev_mas-162_bank_combo_match.md` — Set による二重消費防止の先行事例

## 修正対象ファイル
- `600_report/606_datamart_daily_cf.js` — `dmAppendPipelineCashflow_` 新規追加 + `dmBuildPlanCashflow_` 末尾で呼出
- `100_config/101_sys_config.js` — `03_sys_params` 初期データに 3 キー追加
- `docs/spec/spec_daily_cf.md` — MAS-078 セクション追記

## 実装内容

### A. `606_datamart_daily_cf.js` への追記

1. **`dmBuildPlanCashflow_` の L302(既存 INV ループ終了直後)と L304(実績込みロジック)の間、もしくは L335 `cashEvents.sort` の直前に呼出挿入**:

       // MAS-078: パイプライン見込みの合流
       dmAppendPipelineCashflow_(cashEvents, ctx, ss, opts);

2. **ファイル末尾に新規関数 `dmAppendPipelineCashflow_(cashEvents, ctx, ss, opts)` を追加**:

   手順:

   - a. `opts.actualsOnly === true` なら即 return(`Utils.logInfo` でスキップログ)
   - b. `21_bud_pipeline` シートを取得、存在しなければ return
   - c. `31_wrk_order.参照元ID` から `PIP_` で始まる全 ID を `invoicedPipeIdSet: Set<string>` に登録
   - d. 追加保険: `32_wrk_invoice.摘要` が `【RPA:PIPE】` を含む行の親 ORD → その参照元 ID を同 Set に加算
   - e. パラメータ取得:
     - `threshold = Number(Utils.getSystemParam('PIPE_CF_CONFIDENCE_THRESHOLD', 0.60)) || 0.60`
     - `mode = String(Utils.getSystemParam('PIPE_CF_WEIGHT_MODE', 'threshold')).trim()`
     - `taxRate = Number(Utils.getSystemParam('PIPE_CF_TAX_RATE', 0.10)) || 0.10`
   - f. パイプラインループ:
     - 有効フラグ false の行はスキップ
     - `CF計上 === false` の行はスキップ
     - `invoicedPipeIdSet.has(pipeMgrId)` なら全月スキップ(1 件でも INV あれば除外)
     - `confidence = normalizeConfidence_(row['確度(ヨミ)'])`
     - `weight = mode === 'weighted' ? confidence : (confidence >= threshold ? 1.0 : 0.0)`
     - weight 0 なら行全体スキップ
     - `startYm = Utils.parseDateToYm(row['計上開始年月'])`、空ならスキップ
     - `duration = Math.max(1, Math.min(120, Number(row['継続月数']) || 1))`
     - `spot = Number(row['スポット売上・初期費用']) || 0`
     - `mrr = Number(row['継続月額(MRR)']) || 0`
     - `lagMonths = Number(row['入金ラグ(月)']) || 0`
     - `paymentDay = Number(row['入金日']) || 0`
     - 月ループ(m = 0..duration-1):
       - `billYm = Utils.addMonths(startYm, m)`
       - `dueYm = Utils.addMonths(billYm, lagMonths)`
       - `dueDate = dmResolvePipelinePaymentDate_(dueYm, paymentDay)`(新規ヘルパー)
       - `amountExcl = (m === 0 ? spot : 0) + mrr`
       - `if (amountExcl <= 0) continue;`
       - `amountIncl = Math.round(amountExcl * weight * (1 + taxRate))`
       - `cashEvents.push({...})` — `src: '見込'`, `refId: pipeMgrId`, memo は `【S-06 見込】{pjName} {スポット+MRR1 | MRR(m+1)} [確度=X% × mode] [PIP:pipeMgrId]`
   - g. サマリログ `Utils.logInfo('dmAppendPipelineCashflow_', '見込み合流: 生成=N 件 / 除外(INV起票済)=M 件 / 閾値未満除外=K 件')`

3. **新規ヘルパー関数 `normalizeConfidence_(raw)` を追加**(本仕様書 Step 2 のコード例を逐語的に)

4. **新規ヘルパー関数 `dmResolvePipelinePaymentDate_(ym, paymentDay)` を追加**(本仕様書 Step 2 のコード例を逐語的に)

5. **条件付き書式の追加**(`dmBuildPlanCashflow_` 末尾 L363-367 の条件付き書式 block):

       // MAS-078: src=見込 の行を薄青ハイライト
       var ruleMiko = SpreadsheetApp.newConditionalFormatRule()
         .whenFormulaSatisfied('=$B2="見込"')
         .setBackground('#E3F2FD')
         .setRanges([sheet.getRange(2, 1, Math.max(rows - 1, 1), cols)])
         .build();
       sheet.setConditionalFormatRules([ruleNeg, ruleMiko]);

### B. `100_config/101_sys_config.js` への追記

`03_sys_params` の初期データ定義に以下 3 行を追加(MAS-008 で追加した 2 行と同じ block 内):

       ['PIPE_CF_CONFIDENCE_THRESHOLD', '0.60', 'MAS-078: パイプライン CF 計上の確度閾値(threshold モード)'],
       ['PIPE_CF_WEIGHT_MODE', 'threshold', 'MAS-078: パイプライン CF の加重モード(threshold / weighted)'],
       ['PIPE_CF_TAX_RATE', '0.10', 'MAS-078: パイプライン CF の消費税率(税抜→税込変換)'],

### C. `docs/spec/spec_daily_cf.md` への追記

末尾または既存の「計画 CF」セクションに、本仕様の業務定義・計算式・パラメータ・ソース区分(見込)を記載。

## 制約
- **`dmBuildDailyCashflow_`(実績専用)は touch しない**(本仕様は計画側 `dmBuildPlanCashflow_` のみ)
- **`isActualOnly=true` モード時は必ずスキップ**(実績スナップショットの純粋性を保つ)
- **二重計上防止 Set は必須**(`31_wrk_order.参照元ID` + `32_wrk_invoice.摘要` の両経路)
- **1 件でも INV 起票済なら全月除外**(粒度管理の複雑化を避ける保守的方針)
- **列インデックスのハードコード禁止**(`h.indexOf('...')` で動的取得、-1 なら throw)
- **`Utils.parseDateToYm` / `parseDateToYmd` / `addMonths` を必ず使用**(独自日付パース禁止、失敗パターン #17 継承)
- **`Infinity` / `NaN` を cashEvents に push しない**(amountIncl が有限値であることを `isFinite` で確認推奨)
- **`cashEvents.push` の構造は既存 L293-301 と完全一致**(後続のソート・残高計算が壊れないため)

## エッジケース
1. 入金予定日空欄 → 月末
2. 確度空欄・不正値 → 0 → 除外
3. 過去滞留案件 → 過去日付のまま計上
4. 部分起票 → パイプライン全体除外
5. `CF計上 = false` → 除外
6. スポット 0 × MRR 0 → スキップ
7. 継続月数 0 → 1 に補正
8. 継続月数 > 120 → 120 にクランプ
9. 閾値未満(threshold モード)→ 除外
10. 期待値モード × 確度 100% → 全額
11. 入金日 = 31 × 2 月 → 月末クランプ
12. `PIP_` 以外の管理 ID → 警告ログ
13. `isActualOnly=true` → 全スキップ
14. 非課税案件 → 一律 10% 課税扱い(将来拡張)

## 実データ検証
- `BUD_PIPE` の HEADERS(`101_sys_config.js:662`)確認済
- `31_wrk_order.参照元ID='PIP_NNNN'` 書込(`406_rpa_pipeline.js:149`)確認済
- `cashEvents` 配列構造(`606_datamart_daily_cf.js:293-301`)確認済
- `Utils.getSystemParam` / `parseDateToYm` / `addMonths` は MAS-008 他で使用実績あり

## 動作確認
`npm run push:dev` 後:
1. `setupAllSchemas` を実行し `03_sys_params` に 3 キーが追加されていることを確認
2. `21_bud_pipeline` に確度 60% 以上の見込み案件(INV 未起票)を 2-3 件入力
3. データマート更新(CF タブ更新メニュー)を実行
4. **検証**: 84_cf_daily_plan の B 列「ソース」に `見込` 行が追加されている
5. **検証**: `見込` 行は背景色 `#E3F2FD`(薄青)でハイライト表示されている
6. **検証**: 摘要列に `【S-06 見込】{pjName} ... 確度=X% × threshold [PIP:PIP_NNNN]` が記載
7. **検証**: 対応する 31_wrk_order(参照元ID=PIP_NNNN)を手動で追加すると、次回マート更新で該当見込みが除外される(二重計上防止)
8. **検証**: 確度 30%(閾値 60% 未満)の案件は除外される(threshold モード)
9. **検証**: `03_sys_params.PIPE_CF_WEIGHT_MODE=weighted` に切替 → 確度 30% 案件が期待値 30% で計上される
10. **検証**: `isActualOnly=true` で CF 再生成 → 見込み行は一切出力されない
11. **検証**: MAS-008 の Cash Runway は本機能の影響を受けない(cfOp は 605 系マートから、84 は日次で別経路のため)
12. **検証**: 過去日付の滞留案件(計上開始年月=過去、INV 未起票)が過去日付のまま表示される

### 拡張思考の使用状況

| フェーズ | 拡張思考 | 備考 |
|---------|:--------:|------|
| Set 構築(2 経路マージ)| あり | 漏れなく invoicedPipeIdSet を作る境界条件 |
| 確度正規化 | あり | `"60%"` / `0.6` / `60` 混在対応 |
| 月次展開ループ | なし | 仕様書でループ変数・終了条件確定済 |
| 条件付き書式 | なし | MAS-008 の先行パターン踏襲 |

推奨実行モデル

工程推奨モデル理由
仕様書作成(本ドキュメント)Claude Opus 4.6二重計上防止 Set の漏れなさ、確度加重モード切替、isActualOnly 尊重、入金日算出の境界条件の複合設計
実装Claude Sonnet 4.6仕様書で関数シグネチャ・パラメータ・ループ構造が確定済み。Set 構築の 2 経路統合と日付境界条件に中程度の判断
動作確認ユーザー手動21_bud_pipeline へのテストデータ投入、CF タブ目視確認、03_sys_params のモード切替再テスト

変更履歴

日付変更内容
2026-04-18初版作成

仕様書作成プロンプト(再現性・監査性のため記録)

仕様書作成プロンプト(再現性・監査性のため記録)

展開して表示
<instruction>
【タイムアウト回避・実行原則(v1.7・必ず遵守すること)】
Claude Code が Phase 2 で API ストリーム idle timeout を起こさないための装備:

1. **拡張思考の使い分け**:
   - Phase 1(設計)では拡張思考をフル活用し、ファイル名形式・エッジケース一覧・Step 分割粒度・固有名詞(関数名/シート名/列名/行番号)を完全に確定させる。
   - Phase 2(清書)の各 Step 内では拡張思考を最小限に抑え、Phase 1 で確定済みの内容の書き下しに徹する。出力途中で再考しない。

2. **テキスト報告の禁止**:
   - 「〜を作成します」等の text のみで tool_use なしに turn を終了しない。
   - 説明は 1 文以内。直ちに tool を呼ぶ。

3. **4-5 分割の Write/Edit 実行**:
   - 仕様書作成は以下の Step に分けて実行する:
     - 2-1 骨格 Write(~20行)
     - 2-2 概要〜注意事項 Edit/Bash(~300行)
     - 2-3a エッジケース〜人間検討事項 Edit/Bash(~200行)
     - 2-3b 実装プロンプト〜変更履歴 Edit/Bash(~250行)
     - 2-4 `<details>` にプロンプト全文記録 Edit/Bash(最重量・必ず独立 Step)
   - 1 回の Write/Edit は約 300 行以内を目安にする。

4. **各 Step で何を書くかを具体指示**:
   - 設計判断を Phase 2 実行時に持ち込まないよう、プロンプト内で指定された各 Step の内容(アーキテクチャ・エッジケース等)を忠実に書き下すこと。

======================================================================

あなたはGAS会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
CLIエージェントである「Claude Code」として、上記の原則と以下のフェーズに従い、案件 S-06「84タブにパイプライン売上を合流」の開発仕様書を作成してください。

## Phase 1: 実行前タスク(必読・必ずツールを使用して順次実行)
(※テキストでの状況報告は一切行わず、直ちにツールの使用を開始してください)

1. `docs/_internal/TODO_future.md` を検索し、案件 **S-06** の「案件名」「概要」「期待される効果」「人間が検討すべき事項」を特定・完全に把握する。
2. `CLAUDE.md` と `docs/_internal/failure_patterns.md` を読む。
3. 84タブ(日次CF等)を生成しているデータマート集計ロジック(`600_report/` 配下の該当ファイル、例えば `605_datamart_cf.js` や日次CF構築用ファイル)を読む。
4. 影響を受けるデータアクセス層(`200_data/202_repository.js` の `PipelineRepository` と `InvoiceRepository`)および `000_infra/003_contracts.js`(関連DTO)を読む。
5. 関連する定数(`000_infra/002_constants.js`、`100_config/101_sys_config.js`)を読み、パイプラインの確度や消費税率のパラメータを確認する。
6. パイプライン起票ロジック(`400_domain/` または `406_rpa_pipeline.js` 等)を確認し、パイプラインからINVが生成された際のリレーション(親ID・管理ID等)を把握する。
7. `docs/_internal/dev_spec_prompt_template.md` の Phase 2 構成と実装プロンプトフォーマットを読む。
8. ツール(MCP等)を使って、対象の 84タブ、21タブ(パイプライン)、32タブ(INV)の DDLコード値と実データのカラム構成(特に「確度」「ステータス」「管理ID」)を事前確認する。

## 既存実装の前提知識(車輪の再発明を防ぐ)
- データマートへの出力は、シートのRangeを直接操作するのではなく、必ずRepositoryから取得したDTO配列をインメモリで加工し、2次元配列化して一括書き込みすること。
- 日付のパース・比較や消費税計算は独自に実装せず、`Utils.parseDateToYmd()` 等の既存ヘルパーを利用すること。

## Phase 2: 仕様書の分割作成
出力先: `docs/dev/dev_mas-078_pipeline_cf_integration.md`
**【重要】絶対に1回のツール呼び出しで全内容を出力せず、以下の Step 2-1 〜 2-4 に厳密に分割して実行してください。**

### Step 2-1: 骨格の作成 (File Write)
対象ファイルに、仕様書テンプレートに準拠した見出し(`## 概要`, `## 目的`, `## 現在のコード`, `## 修正方針` 等)の骨格のみを Write ツールで作成して保存してください。本文は空で構いません。

### Step 2-2: 前半セクションの追記 (File Edit または Bash)
「概要」「目的」「現在のコード」「修正方針」「影響範囲」「注意事項」を追記してください。以下を必ず含めること:
- **アーキテクチャの決定事項**:
  - 84タブへの入金予測のデータソースとして、従来の `32_wrk_invoice`(確定請求)の配列に対し、`21_bud_pipeline`(見込み売上)から抽出・変換した仮想のCF入金配列を結合(マージ)させるアプローチを採用する。
  - **確度の適用と消費税計算**: キャッシュフロー予測は「税込入金額」ベースである。パイプラインの税抜金額に対し、消費税を加算した「税込金額」をCF入金予測額とする。その際、「確度加重の閾値」によるフィルタリングを適用する。
  - **二重計上の防止(最重要)**: パイプライン案件がすでに受注・請求段階に進み、`32_wrk_invoice` にも存在している場合、入金見込みが二重に計上される深刻なバグとなる。パイプライン側で「ステータスが受注済以上のものを除外する」、または「Invoice の起票元IDリストをインメモリの Set 等で保持し、`Set.has()` で照合・除外する」ロック処理と同等の排他ロジックを必ず仕様に組み込むこと。
  - **実績モード(isActualOnly)の考慮**: マート集計ロジックが `isActualOnly = true` (実績専用モード)で呼び出された場合、パイプライン(未来の見込み)の結合処理は実行せずスキップする。

### Step 2-3a: エッジケース〜人間検討事項の追記 (File Edit または Bash)
「エッジケース」「実データ検証」「関連ドキュメント」「人間が検討すべき事項」を追記してください。
- **エッジケース(テーブル形式で必須)**:
  1. **日付のフォールバック**: パイプラインの「入金予定日」または「決済予定日」が空欄の場合、発生予定月の月末を自動算出する。
  2. **確度の不正値**: 確度が空欄または文字列の場合、デフォルトを 0% として扱い除外する。
  3. **過去滞留案件**: 入金予定日が過去になってしまったが未起票のパイプラインデータが存在する場合(当月にスライドさせるか、除外するかのルール)。
  4. **部分起票**: パイプラインの一部金額だけが分割でINV起票された場合の残額の扱い(複雑化を避けるため、1件でも紐づくINVがあればパイプライン全体をCFから除外する等の方針を推奨)。
- **プロダクトポリシー**:
  - CF予測額に仮想データが含まれる場合、84タブ上で「確定分」と「見込分(パイプライン)」が一目で区別できるよう、データソース区分列を設けるか備考欄に自動記載し、Human-in-the-Loopの視認性を確保する。
- **実データ検証**:
  - Phase 1(Step 8)で確認した 21タブ(パイプライン)の確度列の存在や、32タブとのリンク関係の状況を記載する。
- **人間が検討すべき事項**:
  - 「確度加重の閾値」の設定(例: 確度60%以上の案件を金額100%で計上するか、金額×確度% の期待値で計上するか)。本仕様では `03_sys_params` 等で閾値をパラメータとして設定可能にすることを提案する。

### Step 2-3b: 実装プロンプト〜変更履歴の追記 (File Edit または Bash)
「実装プロンプト(Claude Code用)」「推奨実行モデル」「変更履歴」を追記してください。
- **実装プロンプト**:
  - バッククォート(```)で囲まず、全行を行頭4スペースインデントで出力すること。
  - 過去の失敗パターン(列インデックスのハードコード禁止、二重計上バグの防止など)を踏まえた注意事項を盛り込むこと。
- **変更履歴**: 当日の日付で「初版作成」と記載する。

### Step 2-4: 仕様書作成プロンプトの記録 (File Edit または Bash)
対象ファイルの末尾に `<details><summary>展開して表示</summary>` を設け、**この `<instruction>` タグの最初から最後まで(今あなたが読んでいるプロンプト全文)**を一言一句そのまま追記して `<details>` を閉じてください。
※この処理が最も出力トークンを消費し重いため、必ず独立したステップとして実行してください。

## Phase 3: `_config.json` への追記と構文チェック
1. `docs/_config.json` の該当箇所(FP&A・ダッシュボード連携等)に今回の仕様書へのリンクと説明を追記して保存。
2. 保存後、ターミナルで `node -e "require('./docs/_config.json')"` 等を実行し、JSONの構文エラー(カンマ抜け、括弧の不整合など)がないか自己チェック・修正する。
</instruction>