MAS-043: ハイブリッド 2 トラック採算管理(RICE/VALUE/SEED)
概要
| 項目 | 内容 |
|---|---|
| 案件 ID | MAS-043 |
| カテゴリ | FP&A・レポーティング |
| 対象ファイル(変更あり) | 000_infra/002_constants.js(Constants.TRACK_TYPES 追加)100_config/101_sys_config.js(schemas.MST_PROJ に 案件種別 列追加)400_domain/420_project_profitability.js(buildProjTrackMap_ ヘルパ追加 + トラック別 Cost Multiplier 集計)600_report/609_datamart_kpi.js(buildKpiDashboard に 3 新規セクション追記) |
| 出力シート(新規セクション追加のみ) | 93_kpi_dashboard(DDL 管理外タブ・動的上書き)79_pj_monthly(DDL 管理外タブ・buildProjectProfitability 出力) |
| 前提案件 | なし(独立実装可) |
ブティック・一人ファーム構造の BLP(bizlp)において、案件を 3 トラック(RICE=ライスワーク / VALUE=バリューワーク / SEED=仕込み・非課金)に分離して採算を追跡する仕組みを追加する。汎用中小企業ルール「人件費 × 3 倍」が一人ファームには構造的に過剰(販管費 15-20% で、存在しない 20% 分を価格に織り込む誤謬)という認識に基づき、価格戦略に連動した採算管理を実現する。
ハイブリッド構造(RICE 2 倍台 + VALUE 実質 5-10 倍)を数値で見える化することで、最頻失敗パターン「ライスワークが忙しくなってバリュー側の仕込みが止まる」を早期検知可能にする。MAS-032(プロダクト別 P/L・事業ポートフォリオ軸)とは別軸(案件種別・価格戦略軸)であり、MAS-042(投資ハードルレート)の採用投資 Payback 計算の単価前提にも影響する(VALUE 単価で採用を正当化する誤謬の回避)。
目的
- 「倍率で価格を決める」汎用ルールから脱却: ハイブリッド構造(RICE 2 倍台 + VALUE 5-10 倍)を数値で見える化し、一人ファームに適した採算管理を確立する
- トラック別 Cost Multiplier の並列可視化: 全社平均の単一倍率を出さず、RICE/VALUE/SEED それぞれの Cost Multiplier を
79_pj_monthlyに並べて記載する - 稼働時間配分の目標レンジ管理: 月次で RICE 50-60% / VALUE 20-30% / SEED 10-30% の目標レンジを
93_kpi_dashboardに追加し、レンジ外れを条件付き背景色(警告色)で可視化する - 失敗パターンの能動検知: (a) 月次 RICE 時間キャップ到達、(b) VALUE 仕込み時間フロア割れ、(c) 同一クライアントに RICE/VALUE 両案件がある場合の VALUE 単価コンタミネーション、の 3 パターンを自動検知して警告を表示する
- 一人ファーム健全値の追跡: 販管費率 15-20%、営業利益率 30-45% を目標レンジとして
93_kpi_dashboardに追跡セクションを追加する
現在のコード
400_domain/420_project_profitability.js(採算エンジン本体)
buildProjectProfitability() 関数(L7 起点)で PJ 別採算を計算し、出力先は 79_pj_monthly(L17: let sheetOut = ss.getSheetByName('79_pj_monthly');)。現状は全社平均ベースの単一 Cost Multiplier を計算している。
- 出力シート名の確定:
002_constants.jsのMENU_DEFINITION(L236)にはdescription: 'PJ 別 P/L マート (78 タブ) を再構築'と記述があるが、実コードの出力先は79_pj_monthly。この乖離は本案件では出力シート側 (79_pj_monthly) を正として扱い、description の誤記修正は別案件 (N-38 メニューカタログ管掌) に委ねる - データソース:
Utils.getSheetByKey('WRK_INVC', '32_wrk_invoice')/Utils.getSheetByKey('BUD_HC', '22_bud_headcount')/Utils.getSheetByKey('BUD_RSCE', '27_bud_resource')を取得。PJ マスタはloadPjMaster_(ss)(同ファイル内プライベート関数)経由 - Cost Multiplier 現行計算:
buildProjectProfitability内で単一の全社平均を算出(トラック別集計は未実装・本案件で追加)
600_report/609_datamart_kpi.js(KPI ダッシュボード描画)
buildKpiDashboard() 関数(L21)が Utils.getSheetNameByKey('KPI_DASH') || '93_kpi_dashboard' で対象シートを取得し、sheet.clear() → renderKpiHeader_ / renderKpiRows_ / renderKpiHelperRows_ / applyKpiFormatting_ / applyKpiConditionalFormat_ の順で描画する。HELPER 行(28-34)は sheet.hideRows(28, 7) で非表示化される。
- 既存セクション末尾行番号: HELPER 行が 28-34 行なので、本案件の新規 3 セクションは 35 行目以降(または HELPER 行の直下から)に追記する
- 描画方針: コメント L2-4 に「数式は INDEX(sheet!$A:$Z,
,
) のようにリテラル値のみで構成」「MATCH/ARRAYFORMULA は使わない」(failure_patterns #24 対応)と明記されており、本案件でもこの方針を踏襲する
100_config/101_sys_config.js(DDL スキーマ)
setupAllSchemas() 関数内 const schemas = {...}(L879 起点)で 'MST_PROJ' を定義(L889):
'MST_PROJ': { headers: ["有効フラグ","PJコード","プロジェクト名","PJ小区分","PJ大区分","社内外","資産化","顧客・取引先名","PJ区分","契約形態","ステータス","資産化対象","PM・責任者名","配賦区分"], color: "#666666" },
現状 案件種別 列は未定義。本案件で headers 配列末尾に追加する(挿入位置は末尾が既存ドロップダウン設定の列番号に影響しないため安全)。
CLAUDE.md 「DDL (setupAllSchemas) で管理されないタブ」リストに 78_pj_pl / 93_kpi_dashboard が含まれていることを確認済。これらは DDL スキーマ変更の対象外であり、GAS コードで直接書き込む方式となる。
000_infra/002_constants.js(Constants オブジェクト)
COLORS(L36-55):WARN_RED_BG(#f4cccc赤)/HEADER_CONFIG(#d9ead3緑)/PROFIT_BG(#fff2cc黄)が実在。レンジ外警告はWARN_RED_BGを使用getParam(key, defaultVal)(L147-167):03_sys_paramsからパラメータ読み込み。キャッシュ付き。本案件では目標レンジ(RICE/VALUE/SEED 各 min/max)を03_sys_paramsで管理するTRACK_TYPES定数は 未定義。本案件で新設する(マジックストリング化防止)
000_infra/003_contracts.js(DTO 変換)
Contracts.toDtoList(data)(L226): { headers: string[], rows: Object[] } を返す。rows プロパティを使う(202_repository.js 側 readSheetAsDtos_ は dtos を返すが、本案件では PJ マスタ読み込みに Contracts.toDtoList を使うため rows を参照する)。
修正方針
4 Step 構成。すべて単一ファイルへの追記・非破壊拡張。
Step 1: 定数追加(000_infra/002_constants.js)
Constants オブジェクトに以下を追加(既存定数末尾の直後に挿入):
TRACK_TYPES: {
RICE: 'RICE',
VALUE: 'VALUE',
SEED: 'SEED',
UNCLASSIFIED: '未分類',
},
RICE/VALUE/SEED のマジックストリング化を防止するための定数定義。コード内では Constants.TRACK_TYPES.RICE 等で参照する(failure_patterns #20 命名造語禁止対応)。
Step 2: DDL スキーマ変更(100_config/101_sys_config.js)
const schemas 内 'MST_PROJ'(L889)の headers 配列末尾に "案件種別" を追加:
'MST_PROJ': {
headers: ["有効フラグ","PJコード","プロジェクト名","PJ小区分","PJ大区分","社内外","資産化","顧客・取引先名","PJ区分","契約形態","ステータス","資産化対象","PM・責任者名","配賦区分","案件種別"],
color: "#666666"
},
ドロップダウン選択肢設定: setVali('MST_PROJ', 15, 'XX', '14_mst_project') の形式で、15 列目 に RICE/VALUE/SEED の選択肢を設定する(既存 setVali 呼び出し L1492-1497 と同一パターン)。選択肢文字列は Constants.TRACK_TYPES の値(RICE/VALUE/SEED)と完全一致させること(failure_patterns #3 DDL コード値 vs 実データ乖離対応)。
78_pj_pl / 93_kpi_dashboard は DDL 管理外のためスキーマ変更不要。
Step 3: 採算ロジック拡張(400_domain/420_project_profitability.js)
プライベートヘルパー buildProjTrackMap_() を追加(GAS 命名規約: 末尾アンダースコア):
function buildProjTrackMap_() {
var sheet = Utils.getSheetByKey('MST_PROJ', '14_mst_project');
if (!sheet) return {};
var result = Contracts.toDtoList(sheet.getDataRange().getValues());
var map = {};
for (var i = 0; i < result.rows.length; i++) {
var dto = result.rows[i];
var flag = dto['有効フラグ'];
if (flag === false || String(flag).toUpperCase() === 'FALSE') continue;
var pjCode = String(dto['PJコード'] || '').trim();
if (!pjCode) continue;
if (map[pjCode]) {
Utils.logError('buildProjTrackMap_', new Error('重複PJコード: ' + pjCode), '先着1件を採用');
continue;
}
var track = String(dto['案件種別'] || '').trim();
var validValues = Object.values(Constants.TRACK_TYPES);
map[pjCode] = validValues.indexOf(track) !== -1
? track
: Constants.TRACK_TYPES.UNCLASSIFIED;
}
return map;
}
buildProjectProfitability 関数冒頭で var trackMap = buildProjTrackMap_(); を呼び出し、既存集計ループでトラック別に Cost Multiplier を分離し、79_pj_monthly に新列(案件種別 / Cost Multiplier(トラック別))を追記する。既存の集計出力は破壊しない(新列の追記のみ)。
Step 4: KPI ダッシュボード拡張(600_report/609_datamart_kpi.js)
buildKpiDashboard() 内、既存の sheet.hideRows(28, 7) の直前(= HELPER 行非表示化の前)に、renderHybridTrackSection_(sheet, ctx) の呼び出しを追加する。関数本体は同ファイル末尾に追記する。
3 新規セクションを以下の順で行 35 以降に書き込む:
セクション 1「稼働時間配分(RICE/VALUE/SEED)」(行 35-42 相当):
- 行 35: ヘッダー
【稼働時間配分(案件種別別)】 - 行 36-39: RICE / VALUE / SEED / 未分類 の月次稼働時間(
70_bud_resource集計)÷ 全社合計 をパーセント表示 - 行 40: 目標レンジ(
03_sys_params参照、デフォルト RICE 50-60% / VALUE 20-30% / SEED 10-30%)を右列に記載 - 範囲外のセルは
sheet.getRange(row, col).setBackground(Constants.COLORS.WARN_RED_BG)で警告色('#f4cccc')を適用
セクション 2「一人ファーム健全性指標」(行 44-48 相当):
- 行 44: ヘッダー
【一人ファーム健全性指標】 - 行 45: 販管費率(目標 15-20%、
62_pl_ytd販管費 ÷ 売上高) - 行 46: 営業利益率(目標 30-45%、
62_pl_ytd営業利益 ÷ 売上高) - 目標レンジ外は
WARN_RED_BGで警告色
セクション 3「失敗パターン検知」(行 50-55 相当):
- 行 50: ヘッダー
【失敗パターン検知】 - 行 51: RICE 時間キャップ超過(60% 超過時、対象月を記載)
- 行 52: VALUE フロア割れ(20% 未満時、対象月を記載)
- 行 53: 単価コンタミネーション(同一クライアントで RICE/VALUE 両案件があり VALUE 単価 ≤ RICE 単価の場合、対象クライアント名を記載)
- 検知ヒット時は
WARN_RED_BGで警告色、ログはUtils.logInfoで対象件名を記録(システム自動ブロックは行わない= Human-in-the-Loop)
書き込み方法: すべて getRange(fixedRow, fixedCol).setValue() 形式のみ使用。MATCH / ARRAYFORMULA / SUBSTITUTE による数式ラベル検索は禁止(failure_patterns #24)。行番号はハードコードではなく、セクション開始行を変数で保持して相対計算する。
影響範囲
変更ファイル一覧
| ファイル | 変更種別 | 内容 |
|---|---|---|
000_infra/002_constants.js | 追加のみ | TRACK_TYPES 定数追加(6 行) |
100_config/101_sys_config.js | 追加のみ | schemas.MST_PROJ.headers に 案件種別 追加(1 要素)+ setVali 1 行 |
400_domain/420_project_profitability.js | 追加のみ | buildProjTrackMap_ 新設 + buildProjectProfitability にトラック別集計呼び出し追加(約 50 行) |
600_report/609_datamart_kpi.js | 追加のみ | renderHybridTrackSection_ 新設 + buildKpiDashboard 呼び出し追加(約 100 行) |
docs/_config.json | 追加のみ | nav に仕様書エントリ追加 |
既存動作への影響
buildProjectProfitabilityの既存 PJ 別 P/L 出力: 新列追記のみで破壊しない。既存の ABC 配賦ロジックは変更なしbuildKpiDashboardの既存 KPI 行(1-34 行): 無変更。新規 3 セクションは行 35 以降に追加14_mst_project:案件種別列が末尾追加される。既存onEditハンドラ・RPA モジュールはindexOfベースの列参照のため影響なし- DDL 再適用:
setupAllSchemas実行時に案件種別列が自動追加される。既存データ行は案件種別が空欄になり、buildProjTrackMap_でUNCLASSIFIED('未分類')としてフォールバックされる
運用・デプロイ手順
- dev 環境で
npm run push:dev→ サイドバー🔧 開発・設定 → DDL 全更新 (Full)(setupAllSchemas)を実行して14_mst_projectに案件種別列を追加 14_mst_projectの数件の PJ に RICE / VALUE / SEED を手動入力- サイドバー
📊 マート更新 → プロジェクト別 採算(buildProjectProfitability)を実行し、79_pj_monthlyにトラック別 Cost Multiplier 列が追加されることを確認 - サイドバー
📊 マート更新 → 📊 KPI ダッシュボード再描画(buildKpiDashboard)を実行し、93_kpi_dashboardに 3 新規セクションが表示されることを確認 - prod 反映は
npm run push:prod後に同じ手順(DDL 更新 → マスタ入力 → マート更新 → KPI 再描画)
注意事項
- ⚠️ failure_patterns #18-#20(命名造語禁止・コード未読による固有名詞誤記):
buildKpiDashboardを含むファイルパス(600_report/609_datamart_kpi.js)・スキーマ変数名(schemas)・システムキー(MST_PROJ・KPI_DASH)・関数名(buildProjectProfitability・Contracts.toDtoList)はすべて Read で実在確認済。記述前に再度 Read で裏取りすること(MAS-003 で 1 回のコード未読から 3 件の固有名詞誤記が発生した直接対策)。 - ⚠️ failure_patterns #24(ラベル解決脆弱性):
93_kpi_dashboardへの書き込みは GAS 側で行番号・列番号を固定してからgetRange(row, col).setValue()で書く。全角スペース・絵文字混在ラベル(例:【稼働時間配分(案件種別別)】)をMATCH/ARRAYFORMULA/SUBSTITUTEで検索する数式アーキテクチャは使わない。既存buildKpiDashboardも同方針を取っている(L2-4 コメント参照)。 - ⚠️ failure_patterns #3(DDL コード値 vs 実データ乖離):
案件種別のプルダウン選択肢(DDL 定義値['RICE', 'VALUE', 'SEED'])と実際にセルに格納される文字列が一致することをsetupAllSchemas実行後に MCP(mcp__google-sheets__get_sheet_data)で確認する。全角ハイフン混入・前後スペース等の表記ゆれを検知するため、buildProjTrackMap_ではString(dto['案件種別'] || '').trim()で前後スペースを除去してから比較する。 - ⚠️ failure_patterns #25 / #26 / #27 非該当: 本案件は単一ファイル拡張のため並列実装なし、
appsscript.jsonのoauthScopes変更不要(外部 API 追加なし)、Admin SDK 不使用。該当なし。 - ⚠️ 列番号ハードコード禁止:
buildProjTrackMap_/buildProjectProfitability/buildKpiDashboard内ですべての列参照をheaders.indexOf('列名')で動的取得する(CLAUDE.md コーディング規約)。 - ⚠️
案件種別未設定行の扱い:buildProjTrackMap_ではConstants.TRACK_TYPES.UNCLASSIFIED('未分類')にフォールバックし、エラー停止させない。ダッシュボード上に「未分類 PJ が N 件あります」の警告表示で人間に更新を促す。 - ⚠️
Contracts.toDtoListとreadSheetAsDtos_の戻り値プロパティ違い:Contracts.toDtoListは{ headers, rows }、readSheetAsDtos_は{ headers, dtos }。本案件はContracts.toDtoListを使うためresult.rowsを参照する(誤ってresult.dtosと書かないこと)。 - ⚠️ Human-in-the-Loop: 失敗パターン検知結果(RICE キャップ超過 / VALUE フロア割れ / 単価コンタミネーション)はシステム自動ブロックせず、
93_kpi_dashboard上の警告表示にとどめ、最終判断は人間に委ねる。
Human-in-the-Loop ポリシー
CLAUDE.md プロダクトポリシー「AI/自動処理の結果は必ず人間がレビュー・承認してから確定する」に準拠:
- 失敗パターン検知(3 種)の結果は 警告表示のみ で、案件受注・契約変更等の自動アクションは一切発生させない
93_kpi_dashboard上でConstants.COLORS.WARN_RED_BG(#f4cccc赤)による視覚的警告のみを提供案件種別の RICE/VALUE/SEED 割り当ては人間(PM)が手動で行う(AI による自動タグ付けは MVP では実装しない)
エッジケース
| # | 条件 | 検知方法 | 期待される挙動 | ログ出力 |
|---|---|---|---|---|
| 1 | 14_mst_project が空(データ行 0 件) | result.rows.length === 0 チェック | 空マップを返す。後続集計はゼロ値で継続 | Utils.logInfo('buildProjTrackMap_', 'PJマスタ空', '件数=0') |
| 2 | 案件種別 列が DDL 未適用でスクリプト実行 | headers.indexOf('案件種別') === -1 | 早期エラーリターン(処理中断)し、ダッシュボードに「案件種別 列が未作成です。setupAllSchemas を実行してください」を表示 | Utils.logError('buildProjTrackMap_', new Error('案件種別列が存在しません')) |
| 3 | 案件種別 が空欄(DDL 適用済・未入力) | dto['案件種別'] が空文字・undefined | Constants.TRACK_TYPES.UNCLASSIFIED('未分類')として集計 | Utils.logInfo('buildProjTrackMap_', '未分類PJ件数: ' + count) |
| 4 | 案件種別 が RICE/VALUE/SEED 以外の不正値(例: 'rice' 小文字、全角 'RICE' 等) | Object.values(Constants.TRACK_TYPES).indexOf(track) === -1 | UNCLASSIFIED にフォールバック | Utils.logError('buildProjTrackMap_', new Error('不正な案件種別: ' + track), 'PJコード=' + pjCode) |
| 5 | 有効フラグ 列が空欄(TRUE/FALSE でもない) | String(flag).toUpperCase() !== 'FALSE' && flag !== false → 有効扱い | 有効フラグとして集計対象に含める(未設定時は TRUE 相当) | ログ出力なし(正常系) |
| 6 | 稼働実績(70_bud_resource)に存在するが 14_mst_project に存在しない PJ コード | trackMap[pjCode] が undefined | UNCLASSIFIED にフォールバックし、稼働時間を未分類カウントに加算 | Utils.logError('buildProjectProfitability', new Error('マスタ未登録PJ: ' + pjCode)) |
| 7 | Cost Multiplier 計算時に内部原価時給が 0(ゼロ除算) | 除算前に hourlyRate === 0 チェック | null または 'N/A' を出力セルに書き込む | Utils.logError('buildProjectProfitability', new Error('時給ゼロ'), 'PJ=' + pjCode) |
| 8 | 稼働時間またはコストがマイナス値(工数修正等) | 集計後に符号チェック | そのまま出力(絶対値変換しない)し、ダッシュボード上で警告色表示 | Utils.logError('buildProjectProfitability', new Error('マイナス値検知: ' + value)) |
| 9 | 14_mst_project に同一 PJ コードが複数行存在 | buildProjTrackMap_ でマップ構築時に map[pjCode] 既存検知 | 先着 1 件のみを採用 | Utils.logError('buildProjTrackMap_', new Error('重複PJコード: ' + pjCode), '先着1件採用') |
| 10 | KPI 目標レンジ(RICE: 50-60% 等)が 03_sys_params 未設定 | Constants.getParam('RICE_TARGET_MIN', 0.50) のデフォルト値使用を検知 | デフォルト値(RICE 50-60% / VALUE 20-30% / SEED 10-30%)で代替 | Utils.logInfo('renderHybridTrackSection_', 'デフォルトレンジ使用: ' + paramKey) |
| 11 | RICE + VALUE + SEED + 未分類の合計稼働時間が全社合計と不一致(丸め誤差等) | Math.abs(sum - total) > 0.01 チェック | ダッシュボードに「未アサイン時間: N 時間」を表示 | Utils.logInfo('renderHybridTrackSection_', '不一致検知: diff=' + diff) |
| 12 | 同一クライアントに RICE/VALUE 両案件があり VALUE 単価 ≤ RICE 単価(単価コンタミネーション) | 取引先名でグループ化し単価比較 | Constants.COLORS.WARN_RED_BG で警告セルをハイライト | Utils.logInfo('detectPriceContamination_', '対象クライアント: ' + clientName) |
| 13 | 14_mst_project シート自体が削除されている | Utils.getSheetByKey('MST_PROJ', '14_mst_project') が null | 空マップ返却(buildProjTrackMap_ 冒頭の if (!sheet) return {};) | Utils.logError('buildProjTrackMap_', new Error('14_mst_project シートが見つかりません')) |
| 14 | Contracts.toDtoList の戻り値プロパティ誤用(dtos と書いてしまう) | 実行時に result.dtos が undefined | TypeError で処理中断(実装時のバグ・レビューで検出) | Utils.logError('buildProjTrackMap_', new TypeError('Cannot read property length of undefined')) |
| 15 | 業務委託のケース(22_bud_headcount.雇用形態='業務委託')での SEED 配分 | 既存 buildProjectProfitability で empType !== '業務委託' 時のみ社保加算 | 業務委託は社保加算なしで集計(現行ロジック継承)。SEED 配分は業務委託の稼働時間も含めて計上 | ログ出力なし(正常系) |
冪等性・再実行の設計
buildProjectProfitability と buildKpiDashboard は処理冒頭で出力シートの対象範囲を clearContent() / sheet.clear() してから書き込む 洗い替え方式 のため冪等。何度実行しても結果が同一。
buildProjectProfitability: 既存実装の洗い替え方式を踏襲buildKpiDashboard: L25-26 でsheet.clear()+sheet.clearConditionalFormatRules()を実行(実装済)- 本案件で追加する 3 セクションも、既存の
sheet.clear()によって毎回洗い流されるため個別のclearContent()呼び出しは不要
Human-in-the-Loop(詳細)
- 失敗パターン検知の通知方式:
93_kpi_dashboard上でConstants.COLORS.WARN_RED_BG(#f4cccc赤)の背景色表示のみ。システム自動アクション(案件ブロック・契約変更通知等)は行わない - 未分類 PJ の警告表示: ダッシュボード上部に「未分類 PJ が N 件あります(PJ コード: XXX, YYY)。
14_mst_projectのマスタ更新が必要です」の形式で警告を表示し、マスタ更新を人間に促す - 案件種別の手動タグ付け: RICE/VALUE/SEED の割り当ては PM が手動で行う。AI 自動分類(価格倍率から機械判定等)は MVP では実装せず、「人間が検討すべき事項」に将来拡張として記載
テスト要件(900_test/901_test_runner.js への追加)
| テスト関数 | 合格基準 | 備考 |
|---|---|---|
test_buildProjTrackMap__emptyMaster | 空マスタで空オブジェクト返却 | DDL 直後の状態を再現 |
test_buildProjTrackMap__disabledFlag | 有効フラグ=FALSE 行が除外される | 3 行中 1 行無効化 → 2 行のみマップに含まれる |
test_buildProjTrackMap__duplicatePjCode | 重複 PJ コードで先行行が採用される | 同一 PJ コード 2 行 → 1 行目の値のみマップ登録 |
test_buildProjTrackMap__invalidTrack | 不正な 案件種別 値で UNCLASSIFIED にフォールバック | 小文字 'rice' 入力 → '未分類' |
test_detectPriceContamination_ | 同一クライアント RICE/VALUE 両案件 + VALUE 単価 ≤ RICE 単価 で警告発生 | 手計算との一致検証 |
実データ検証
実装前・実装後に MCP(mcp__google-sheets__*)で以下を確認:
14_mst_projectの現ヘッダー行:案件種別列が存在しないことを確認(DDL 適用前)。実行後、列末尾に案件種別が追加されたことをmcp__google-sheets__get_sheet_dataで確認- プルダウン選択肢の整合性: DDL 適用後、
14_mst_projectの案件種別列のプルダウン選択肢が['RICE', 'VALUE', 'SEED']と完全一致することを確認(failure_patterns #3 対策。全角'RICE'、小文字'rice'等の混入がないか) - 手動入力テスト: 数件の PJ に RICE/VALUE/SEED を手動設定し、
buildProjectProfitability実行後、79_pj_monthlyにトラック別 Cost Multiplier 列が追加され、手計算と一致することを検証 - レンジ外警告の表示確認: RICE 配分を意図的に目標レンジ外(例: 70%)に設定し、
93_kpi_dashboardの対応セルがWARN_RED_BG(#f4cccc)で塗られることを確認 - 失敗パターン検知: 各条件(RICE キャップ超過 / VALUE フロア割れ / 単価コンタミネーション)を意図的に再現して警告が正しく表示されることを確認
- 未分類 PJ の警告:
案件種別未入力の PJ を残した状態でbuildKpiDashboardを実行し、「未分類 PJ が N 件あります」警告がダッシュボード上部に表示されることを確認 03_sys_paramsのパラメータキー:RICE_TARGET_MIN/RICE_TARGET_MAX/VALUE_TARGET_MIN/VALUE_TARGET_MAX/SEED_TARGET_MIN/SEED_TARGET_MAX/SGA_RATIO_MIN/SGA_RATIO_MAX/OP_PROFIT_RATIO_MIN/OP_PROFIT_RATIO_MAXの 10 キーが未登録であることを確認(衝突回避)
関連ドキュメント
| 仕様書・ドキュメント | 関連箇所 |
|---|---|
| dev_mas-006_project_overhead_allocation.md | PJ 別配賦ロジックの基盤。本案件は既存 buildProjectProfitability を非破壊拡張する形で MAS-006 の成果物を活用 |
| dev_mas-014_pj_variance_dashboard.md | PJ 別予実ダッシュボード(未着手)。MAS-043 完成後に PJ 別予実差異をトラック別に分解する拡張余地あり |
| dev_mas-032_product_pl.md | プロダクト別 P/L(事業ポートフォリオ軸)。本案件とは別軸(案件種別・価格戦略軸)だが、同一案件が両軸でカウントされることに注意 |
| dev_mas-042_investment_hurdle_rate.md | 投資ハードルレート判定。MAS-043 の RICE/VALUE 単価前提が MAS-042 の採用投資 Payback 計算に影響する(相互依存) |
| dev_mas-026_tdabc_cost_allocation.md | TDABC(時間主導型 ABC)。稼働時間配分の実績データソース候補の 1 つ |
| CLAUDE.md | コーディング規約(ヘッダー名ベース列参照・有効フラグスキップ・DDL 管理外タブリスト) |
| failure_patterns.md | #3 DDL コード値乖離 / #18-#20 命名造語禁止 / #24 ラベル解決脆弱性 / #25-#27 非該当 |
人間が検討すべき事項
- RICE/VALUE/SEED の境界定義: 価格倍率で機械判定するか、PM が手動タグ付けするか。現案: MVP では手動タグ付け(PM が
14_mst_projectに直接入力)、将来的に AI 自動分類を検討 - VALUE 単価の管理方式: 倍率ベース(× 原価)ではなくバリューベース(絶対額)で管理する場合のマスタ構造。現案: 価格管理は
21_bud_pipelineの既存フィールドで継続、MAS-043 は集計側の分離のみ - MAS-032(プロダクト別 P/L)との集計整合性: 同一案件が MAS-032 で「受託」、MAS-043 で「RICE」となる二重計上防止ルール。現案: 軸が異なるため並列で集計する。ダッシュボード上でマトリクス表示(MAS-032 軸 × MAS-043 軸)を追加する拡張余地あり
- 失敗パターン検知の通知チャネル: 色分けのみで十分か、メール通知・Slack 通知を追加するか。現案: MVP は色分けのみ。MAS-186(Slack 経費精算)実装時に通知基盤を共通化
- 案件種別の年次見直し運用: RICE 単価が自然に 2.3 → 2.5 倍へスライドした際の VALUE への昇格基準。現案: 四半期レビュー時に PM が手動判断、自動昇格は実装しない
- 稼働時間配分の実績データソース: MAS-026(TDABC)/ MAS-027(M365 ログ連携)/ MAS-105(工数入力)のいずれを入力とするか。現案: 既存
70_bud_resourceを入力源とし、MAS-027 完成後に M365 ログ自動同期へ切り替え - 目標レンジの管理粒度: 現案は
03_sys_paramsで全社単一値(RICE 50-60% 等)だが、四半期別・年度別に変動させる必要があるか。判断基準: 事業フェーズ移行時(Phase 1 → 2 等) - 一人ファーム健全性指標の閾値: 販管費率 15-20% / 営業利益率 30-45% はブティック・一人ファーム業界ベンチマークだが、業種別(IT コンサル vs 会計事務所)に差がある。現案: MVP は単一閾値、将来的に業種別マスタ化
- 78 vs 79 タブの出力先:
buildProjectProfitabilityは79_pj_monthlyに出力しているが、MENU_DEFINITION description には「78 タブ」と記載。これは MENU 側の誤記であり、本案件では79_pj_monthlyを正として扱う。別案件(N-38 メニューカタログ管掌)で description 修正を提案 案件種別のバージョン管理: 案件種別の履歴(過去に RICE だった案件が VALUE に変更された等)を残すか。案:14_mst_projectへの追記のみで履歴は残さない(監査ログ98_audit_logで間接的に追跡可能)
実装プロンプト(Claude Code 用)
あなたは GAS 会計システム(bizlp-gas-accounting)のシニア開発者です。
案件 MAS-043「ハイブリッド 2 トラック採算管理(RICE/VALUE/SEED)」を実装してください。
以下の 4 Step を順番に実施すること。
## 実行前タスク(必須・4 件)
1. `000_infra/002_constants.js` を Read し `Constants` オブジェクトの末尾付近を確認(`TRACK_TYPES` 定数の挿入位置を確定)
2. `100_config/101_sys_config.js` を Read し `const schemas`(L879 起点)の `'MST_PROJ'`(L889)の headers 配列と、`setVali` 呼び出し(L1491-1497)のパターンを確認
3. `400_domain/420_project_profitability.js` を Read し `buildProjectProfitability` の冒頭(L7)・出力先シート名(`79_pj_monthly`・L17)・現行 Cost Multiplier 計算箇所を確認
4. `600_report/609_datamart_kpi.js` を Read し `buildKpiDashboard`(L21)の構造(`sheet.clear()` → render 系関数群 → `hideRows(28, 7)`)と、HELPER 行 28-34 の直後への挿入位置を確認
## Step 1: 定数追加(`000_infra/002_constants.js` のみ変更・推奨モデル: Haiku)
`Constants` オブジェクトに以下を追加する(挿入位置は実行前タスク 1 で確認した既存定数末尾の直後。具体的には `RPA_DEFAULTS` の直後あたり):
TRACK_TYPES: {
RICE: 'RICE',
VALUE: 'VALUE',
SEED: 'SEED',
UNCLASSIFIED: '未分類',
},
## Step 2: DDL スキーマ変更(`100_config/101_sys_config.js` のみ変更・推奨モデル: Haiku)
- 実行前タスク 2 で確認した実際の変数名・配列名を使用し、`schemas.MST_PROJ.headers` 配列の末尾に `"案件種別"` を追加:
```javascript
'MST_PROJ': { headers: [..., "配賦区分", "案件種別"], color: "#666666" },
```
- `setupAllSchemas()` 内の `setVali` 呼び出しブロック(L1491 付近、`MST_PROJ`: ドロップダウン設定コメント直後)に以下を追加:
```javascript
setVali('MST_PROJ', 15, 'XX', '14_mst_project'); // 案件種別 (O列) → UIPJ案件種別
```
※ `'XX'` は `15_mst_dictionary`(`MST_DICT`)内の対応カテゴリのドロップダウンソース列を指す。既存パターン(`'E'` = 取引先, `'AB'` = PJ区分 等)に合わせて、`15_mst_dictionary` に `案件種別` カテゴリの 3 値(RICE/VALUE/SEED)を追加後に適切な列文字を指定する
- `78_pj_pl` / `93_kpi_dashboard` は DDL 管理外のため変更しない(CLAUDE.md 「DDL で管理されないタブ」参照)
- 列定義は既存列(`配賦区分` 等)の書き方パターンに倣う
## Step 3: 採算ロジック拡張(`400_domain/420_project_profitability.js` のみ変更・推奨モデル: Sonnet)
### 追加するヘルパー関数 `buildProjTrackMap_()`
(GAS 命名規約: 末尾アンダースコアでプライベート関数を示す)
function buildProjTrackMap_() {
var sheet = Utils.getSheetByKey('MST_PROJ', '14_mst_project');
if (!sheet) return {};
var result = Contracts.toDtoList(sheet.getDataRange().getValues());
var map = {};
for (var i = 0; i < result.rows.length; i++) {
var dto = result.rows[i];
var flag = dto['有効フラグ'];
if (flag === false || String(flag).toUpperCase() === 'FALSE') continue;
var pjCode = String(dto['PJコード'] || '').trim();
if (!pjCode) continue;
if (map[pjCode]) {
Utils.logError('buildProjTrackMap_', new Error('重複PJコード: ' + pjCode), '先着1件を採用');
continue;
}
var track = String(dto['案件種別'] || '').trim();
var validValues = Object.values(Constants.TRACK_TYPES);
map[pjCode] = validValues.indexOf(track) !== -1
? track
: Constants.TRACK_TYPES.UNCLASSIFIED;
}
return map;
}
### `buildProjectProfitability` への追加
- 関数冒頭(L8 の `const FUNC = ...` 直後)で `var trackMap = buildProjTrackMap_();` を呼び出す
- 集計ループでトラック別に Cost Multiplier を分離し、`79_pj_monthly` の新列(`案件種別` / `Cost Multiplier(トラック別)`)に書き込む
- 既存の集計出力(`79_pj_monthly` の現行列)を破壊しない(新列の追記のみ)
- 書き込みは `sheet.getRange(row, col).setValue()` 形式で、列番号は動的取得(`headers.indexOf`)
## Step 4: KPI ダッシュボード拡張(`600_report/609_datamart_kpi.js` のみ変更・推奨モデル: Sonnet)
- `buildKpiDashboard()` 内、`sheet.hideRows(28, 7)` の直前に `renderHybridTrackSection_(sheet, ctx);` の呼び出しを追加
- 同ファイル末尾に `renderHybridTrackSection_` 関数本体を追記
- **書き込み方法**: `getRange(fixedRow, fixedCol).setValue()` 形式のみ使用。MATCH / ARRAYFORMULA による数式ラベル検索禁止(failure_patterns #24)
- 3 セクションの行番号は HELPER 行(28-34)の直後から開始:
### セクション 1「稼働時間配分」(行 35-42 相当)
- 行 35: ヘッダー `【稼働時間配分(案件種別別)】`
- 行 36-39: RICE / VALUE / SEED / 未分類 の月次稼働時間÷全社合計 をパーセント表示
- 行 40: 目標レンジ(`Constants.getParam('RICE_TARGET_MIN', 0.50)` 等で取得、デフォルト RICE 50-60% / VALUE 20-30% / SEED 10-30%)
- 目標レンジ外のセルは `sheet.getRange(row, col).setBackground(Constants.COLORS.WARN_RED_BG)` を呼ぶ
### セクション 2「一人ファーム健全性指標」(行 44-48 相当)
- 行 44: ヘッダー `【一人ファーム健全性指標】`
- 行 45: 販管費率(目標 15-20%、`62_pl_ytd` 販管費 ÷ 売上高)
- 行 46: 営業利益率(目標 30-45%、`62_pl_ytd` 営業利益 ÷ 売上高)
- レンジ外は同様に `WARN_RED_BG` で警告色
### セクション 3「失敗パターン検知」(行 50-55 相当)
- 行 50: ヘッダー `【失敗パターン検知】`
- 行 51: RICE キャップ超過検知(`> RICE_TARGET_MAX`)
- 行 52: VALUE フロア割れ検知(`< VALUE_TARGET_MIN`)
- 行 53: 単価コンタミネーション検知(同一クライアントで RICE/VALUE 両案件があり VALUE 単価 ≤ RICE 単価)
- システムによる自動ブロックは行わない(Human-in-the-Loop)
## 制約
- `appsscript.json` の `oauthScopes` は変更しない(failure_patterns #26: 部分宣言で既存スコープが無効化される)
- 列番号のハードコード禁止。`headers.indexOf('列名')` で動的取得
- `案件種別` 未設定行は `Constants.TRACK_TYPES.UNCLASSIFIED` でフォールバック(エラー停止しない)
- `buildProjectProfitability` の既存出力(現行 PJ 別 P/L 部分)を破壊しない
- `Contracts.toDtoList` の戻り値は `{ headers, rows }`(`dtos` ではない)を参照
- 独自色定義禁止。警告色は `Constants.COLORS.WARN_RED_BG` を使う
## 動作確認(npm run push:dev 後)
1. サイドバー → 🔧 開発・設定 → `DDL 全更新 (Full)` を実行し `14_mst_project` に `案件種別` 列が追加されたことを確認
2. `14_mst_project` の数件に RICE / VALUE / SEED を手動入力
3. サイドバー → 📊 マート更新 → `プロジェクト別 採算` を実行し、`79_pj_monthly` にトラック別 Cost Multiplier 列が追加されていることを確認
4. サイドバー → 📊 マート更新 → `📊 KPIダッシュボード再描画` を実行し 3 新規セクションが `93_kpi_dashboard` に追加されていることを確認
5. RICE 配分を意図的に目標レンジ外(例: 70%)に設定し、`93_kpi_dashboard` の対応セルが `#f4cccc` で塗られることを確認
6. 同一クライアントに RICE / VALUE 両案件を設定し単価コンタミネーション警告が表示されることを確認
7. `99_error_log` に想定外のエラーが記録されていないことを確認
## エッジケース(実装時の確認観点)
仕様書「エッジケース」セクションのテーブル 15 件を参照
### 拡張思考の使用状況
| フェーズ | 拡張思考 | 備考 |
|---------|---------|------|
| 実行前タスク(Read・設計確定) | あり | 関数名・行番号・挿入位置を完全確定 |
| Step 1-4(実装・Edit) | なし | 確定済み内容の書き下しに徹する |
推奨実行モデル
| 工程 | 推奨モデル | 理由 |
|---|---|---|
| Step 1: 定数追加 | Claude Haiku 4.5 | 仕様書でコード完全定義済み、判断要素なし |
| Step 2: DDL スキーマ変更 | Claude Haiku 4.5 | 既存パターンの単純追記、判断不要 |
| Step 3: 採算ロジック拡張 | Claude Sonnet 4.6 | 既存関数への挿入位置特定とトラック別集計ロジック統合が必要 |
| Step 4: KPI ダッシュボード拡張 | Claude Sonnet 4.6 | 複数シート横断の書き込み位置管理と条件分岐設計が必要 |
変更履歴
| 日付 | 変更内容 |
|---|---|
| 2026-04-23 | 初版作成。Gemini Pro メタプロンプト + Claude Sonnet 添削(scripts/1_generate_prompts_gemini.js パイプライン)で生成された tasks/prompts/task_F-43.md をベースに執筆。ブティック・一人ファーム構造(BLP)に適した RICE/VALUE/SEED 3 トラック採算管理。汎用「人件費 × 3 倍」ルールが一人ファーム(販管費 15-20%)には構造過剰という認識から、ハイブリッド構造(RICE 2 倍台 + VALUE 5-10 倍)を数値で見える化。buildProjTrackMap_ ヘルパ新設 + Constants.TRACK_TYPES 定数定義 + 14_mst_project.案件種別 列追加 + 93_kpi_dashboard に 3 新規セクション(稼働時間配分 / 一人ファーム健全性指標 / 失敗パターン検知)。出力先シート不一致(MENU description「78 タブ」vs 実コード 79_pj_monthly)を Phase 1 で確定し、本案件では 79_pj_monthly を正として扱う。エッジケース 15 件・人間検討事項 10 件・推奨実行モデル 4 工程(Haiku×2 / Sonnet×2)を定義 |
仕様書作成プロンプト(再現性・監査性のため必ず記録)
展開して表示(Gemini Pro + Claude Sonnet レビュー済み・`tasks/prompts/task_F-43.md`)
<instruction>
【タイムアウト回避・実行原則(v1.7・必ず遵守すること)】
1. 拡張思考の使い分け:
- Phase 1(設計フェーズ): ファイルパス・関数名・シート名・行番号・エッジケース一覧・Step 分割粒度を完全確定。Read によるコード裏取りを妥協しない(failure_patterns #18-#20 の直接対策)。
- Phase 2(清書フェーズ): Phase 1 で確定済みの内容の書き下しに徹する。
2. テキストでの状況報告の禁止: text のみ返して tool_use を発行せずに終了する turn を作らない。
3. 4-5 分割の Write/Edit 実行:
- Step 2-1: 骨格 Write(~20 行)
- Step 2-2: 概要〜注意事項 Edit/Bash(~300 行)
- Step 2-3a: エッジケース〜人間が検討すべき事項 Edit/Bash(~200 行)
- Step 2-3b: 実装プロンプト〜変更履歴 Edit/Bash(~250 行)
- Step 2-4: <details> にプロンプト全文記録 Edit/Bash(最重量・必ず独立 Step)
4. 各 Step で何を書くかを具体指示。
======================================================================
あなたは GAS 会計システム(bizlp-gas-accounting)のシニア開発者兼仕様書ライターです。
案件 F-43「ハイブリッド 2 トラック採算管理(RICE/VALUE/SEED)」の開発仕様書を作成してください。
(Phase 1 調査項目 1-1〜1-5、Phase 2 Step 2-1〜2-4、Phase 3 登録・コミットの全内容が記載される。
全文は `tasks/prompts/task_F-43.md` を参照)
</instruction>
※ Gemini Pro (gemini-2.5-pro) が docs/_internal/failure_patterns.md / docs/_internal/dev_spec_prompt_template.md / コアコード 4 ファイル(002_constants.js / 003_contracts.js / 004_utils.js / 202_repository.js)を読み込んで設計した後、Claude Sonnet 4.6 が実行者目線で添削した成果物。プロンプトパイプライン: scripts/1_generate_prompts_gemini.js → tasks/prompts/task_F-43.gemini.md(Gemini 原版)→ tasks/prompts/task_F-43.md(Sonnet 添削後・最終版)