プロジェクト予算管理
概要
プロジェクト単位で予算上限を設定し、Issue報酬の消化状況をリアルタイムに把握する。予算超過・進捗遅延を早期検知してアラートを発行する。
1. プロジェクト予算上限の設定
1.1 予算設定ルール
| 項目 | 説明 |
|---|---|
| 設定権限 | マネージャーのみ |
| 設定タイミング | プロジェクト作成時、または任意のタイミングで変更可能 |
| 単位 | 円 |
| 変更履歴 | 全て記録(監査対応) |
1.2 予算の構成
プロジェクト予算上限(budget_limit)
├── 確定済み消化額(confirmed_total) ... confirmed + paid の報酬合計
├── 保留中消化額(pending_total) ... pending の報酬合計
├── 予約済み消化額(active_total) ... active の報酬合計
└── 残予算(remaining) ... budget_limit - (confirmed_total + pending_total + active_total)2. 消化額のリアルタイム集計ロジック
2.1 消化額の定義
sql
-- 消化額(支払い確定 + 保留中)
SELECT
SUM(CASE WHEN status IN ('confirmed', 'paid') THEN amount ELSE 0 END)
AS confirmed_total,
SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END)
AS pending_total,
SUM(CASE WHEN status = 'active' THEN amount ELSE 0 END)
AS active_total,
SUM(CASE WHEN status IN ('confirmed', 'paid', 'pending', 'active') THEN amount ELSE 0 END)
AS total_consumed
FROM rewards
WHERE project_id = :project_id;2.2 各指標の算出
| 指標 | 計算式 |
|---|---|
| 残予算 | budget_limit - (confirmed_total + pending_total + active_total) |
| 予算消化率 | (confirmed_total + pending_total) / budget_limit * 100 |
| Issue消化率 | completed_issues / total_issues * 100 |
注: 予算消化率の分子には
activeを含めない。確定的な消化(confirmed + pending)のみで算出する。activeは参考値として別途表示する。
2.3 集計クエリ
sql
-- プロジェクト予算サマリー
SELECT
p.id AS project_id,
p.name AS project_name,
p.budget_limit,
COALESCE(r.confirmed_total, 0) AS confirmed_total,
COALESCE(r.pending_total, 0) AS pending_total,
COALESCE(r.active_total, 0) AS active_total,
p.budget_limit - COALESCE(r.confirmed_total, 0)
- COALESCE(r.pending_total, 0)
- COALESCE(r.active_total, 0) AS remaining_budget,
ROUND(
(COALESCE(r.confirmed_total, 0) + COALESCE(r.pending_total, 0))
* 100.0 / NULLIF(p.budget_limit, 0), 1
) AS budget_consumption_rate,
ROUND(
i.completed_count * 100.0 / NULLIF(i.total_count, 0), 1
) AS issue_completion_rate
FROM projects p
LEFT JOIN (
SELECT
project_id,
SUM(CASE WHEN status IN ('confirmed', 'paid') THEN amount ELSE 0 END) AS confirmed_total,
SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END) AS pending_total,
SUM(CASE WHEN status = 'active' THEN amount ELSE 0 END) AS active_total
FROM rewards
GROUP BY project_id
) r ON r.project_id = p.id
LEFT JOIN (
SELECT
project_id,
COUNT(*) AS total_count,
COUNT(*) FILTER (WHERE status = 'closed') AS completed_count
FROM issues
GROUP BY project_id
) i ON i.project_id = p.id;3. アラート設計
3.1 予算超過アラート
| レベル | 条件 | アクション |
|---|---|---|
| 注意 | 予算消化率 >= 70% | マネージャーに通知 |
| 警告 | 予算消化率 >= 90% | マネージャー + PMに通知 |
| 危険 | 残予算 < 0(超過) | 新規Issue報酬設定をブロック、全関係者に通知 |
3.2 予算消化率 vs Issue消化率の乖離アラート
予算の消化ペースがIssue完了ペースを大きく上回っている場合、高額Issueへの偏りやスコープ変更の兆候と判断する。
乖離度 = 予算消化率 - Issue消化率| 条件 | レベル | 意味 |
|---|---|---|
| 乖離度 >= 20% | 警告 | 予算消化が進捗に対して速すぎる |
| 乖離度 >= 30% | 危険 | 予算枯渇リスクが高い。PMへ即座にエスカレーション |
| 乖離度 <= -20% | 情報 | Issue消化が予算消化を上回っている(健全) |
乖離アラートの例
プロジェクト: Backend API開発
予算消化率: 65%
Issue消化率: 40%
乖離度: +25% ⚠️ 警告
→ 高額Issueが先に消化されている可能性。
残Issueの報酬合計が残予算内に収まるか確認してください。3.3 アラート判定ロジック
sql
-- 乖離アラート対象プロジェクト抽出
SELECT
project_id,
budget_consumption_rate,
issue_completion_rate,
(budget_consumption_rate - issue_completion_rate) AS divergence
FROM project_budget_summary
WHERE (budget_consumption_rate - issue_completion_rate) >= 20
ORDER BY divergence DESC;4. 月次スナップショット保存
4.1 目的
- 月末時点の予算状況を履歴として保存
- 時系列での予算消化トレンド分析
- 監査対応
4.2 スナップショットデータ
sql
-- 月次スナップショット保存(毎月末日バッチ実行)
INSERT INTO budget_snapshots (
project_id,
snapshot_date,
budget_limit,
confirmed_total,
pending_total,
active_total,
remaining_budget,
budget_consumption_rate,
issue_total_count,
issue_completed_count,
issue_completion_rate,
divergence,
created_at
)
SELECT
p.id,
CURRENT_DATE,
p.budget_limit,
COALESCE(r.confirmed_total, 0),
COALESCE(r.pending_total, 0),
COALESCE(r.active_total, 0),
p.budget_limit - COALESCE(r.confirmed_total, 0)
- COALESCE(r.pending_total, 0) - COALESCE(r.active_total, 0),
ROUND((COALESCE(r.confirmed_total, 0) + COALESCE(r.pending_total, 0))
* 100.0 / NULLIF(p.budget_limit, 0), 1),
COALESCE(i.total_count, 0),
COALESCE(i.completed_count, 0),
ROUND(COALESCE(i.completed_count, 0) * 100.0 / NULLIF(i.total_count, 0), 1),
ROUND((COALESCE(r.confirmed_total, 0) + COALESCE(r.pending_total, 0))
* 100.0 / NULLIF(p.budget_limit, 0), 1)
- ROUND(COALESCE(i.completed_count, 0) * 100.0 / NULLIF(i.total_count, 0), 1),
NOW()
FROM projects p
LEFT JOIN (/* rewards集計サブクエリ */) r ON r.project_id = p.id
LEFT JOIN (/* issues集計サブクエリ */) i ON i.project_id = p.id;4.3 budget_snapshots テーブル
| カラム | 型 | 説明 |
|---|---|---|
| id | UUID | 主キー |
| project_id | UUID | FK: projects.id |
| snapshot_date | DATE | スナップショット日 |
| budget_limit | INTEGER | 予算上限 |
| confirmed_total | INTEGER | 確定済み消化額 |
| pending_total | INTEGER | 保留中消化額 |
| active_total | INTEGER | 予約済み消化額 |
| remaining_budget | INTEGER | 残予算 |
| budget_consumption_rate | DECIMAL | 予算消化率(%) |
| issue_total_count | INTEGER | 全Issue数 |
| issue_completed_count | INTEGER | 完了Issue数 |
| issue_completion_rate | DECIMAL | Issue消化率(%) |
| divergence | DECIMAL | 乖離度(%) |
| created_at | TIMESTAMP | 作成日時 |
5. データモデル(予算関連)
5.1 projects テーブル(予算カラム追加)
| カラム | 型 | 説明 |
|---|---|---|
| budget_limit | INTEGER | 予算上限(円) |
| budget_alert_threshold | INTEGER | アラート閾値(%)。デフォルト70 |
5.2 budget_change_logs テーブル
| カラム | 型 | 説明 |
|---|---|---|
| id | UUID | 主キー |
| project_id | UUID | FK: projects.id |
| previous_limit | INTEGER | 変更前の予算上限 |
| new_limit | INTEGER | 変更後の予算上限 |
| changed_by | UUID | FK: users.id |
| reason | TEXT | 変更理由 |
| created_at | TIMESTAMP | 変更日時 |