リソース平準化と制約条件の「衝突」を制御せよ:Project VBAにおける高度なスケジューリング・ロジックの設計思想
「リソース平準化を実行したら、絶対に動かしてはいけないはずの制約条件(Must Start On等)が無視された、あるいは逆に、制約が強すぎて平準化が全く機能せず、リソースの過負荷が解消されない……」
もし君が、MS Projectの「リソース平準化」ボタンをVBAからただ叩くだけのコードを書いているとしたら、それはエンジニアではなく、単なる「スクリプター」だ。実務におけるスケジューリング・エンジンは、そんな単純なものではない。
制約条件(Constraints)とリソースの可用性(Availability)は、常にトレードオフの関係にある。この二つの「力」が衝突した際、どちらを優先し、どのようにスケジュールを再構成するか。その意思決定ロジックをコードに落とし込むことこそが、真の業務自動化エンジニアに求められる職能だ。
本稿では、中級者への階段を登る君たちに向けて、リソース平準化と制約条件の競合を、VBAを用いていかに「制御」すべきか、その極限の設計思想を伝授する。
1. なぜ、単純な平準化は「失敗」するのか
MS Projectの計算エンジンは、極めて強力だが、盲目的だ。
1. 制約条件(Hard Constraints)の絶対性: `Must Start On` や `Must Finish On` は、エンジンにとって「絶対的な壁」である。平準化エンジンは、リソースがいくら不足していても、この壁を越えてタスクを動かすことはできない。結果として、リソースの過負荷(Overallocation)が解消されないまま残る。
2. 制約の連鎖(Constraint Propagation): 一つのタスクに強い制約をかけると、その先行・後続関係を通じて、プロジェクト全体のクリティカルパスが歪み、平準化の「遊び(Float)」を奪い去ってしまう。
実務で求められるのは、「制約を一時的に緩和(Relaxation)し、平準化を行い、その結果を評価して再度制約を適用する」という、反復的なステート管理(State Management)のロジックである。
2. アーキテクチャ設計:緩和・平準化・評価のループ
堅牢なツールを設計する場合、単発の処理ではなく、以下のサイクルを実装すべきだ。
ステップ1:制約の棚卸しと分類
すべての制約を等しく扱うのは愚策だ。
- Hard Constraints: `Must Start On`, `Must Finish On`(原則、変更不可)
- Soft Constraints: `Start No Earlier Than`, `Finish No Later Than`(平準化のために緩和可能)
ステップ2:緩和(Relaxation)フェーズ
リソース過負荷が発生しているタスクのうち、`Soft Constraints` を持っているものに対し、一時的に制約を `As Soon As Possible (ASAP)` へと書き換える。
ステップ3:エンジン実行(Leveling)
エンジンの計算モードを最適化した状態で、平準化を実行する。
ステップ4:整合性チェックとロールバック
平準化後のスケジュールが、ビジネス上の許容範囲(例:納期遅延の許容限界)を超えていないかを検証する。
3. プロダクション・コード:制約緩和型平準化エンジン
以下に、実務でそのまま使えるレベルの、堅牢な設計思想に基づいたコード例を示す。
単なるコピペではなく、「計算モードの制御」と「エラーハンドリング」、そして「オブジェクトのライフサイクル」に注目して読んでほしい。
Option Explicit
' 制約条件の優先度を定義する列挙型
' これにより、どの制約を「守るべき」とし、どの制約を「妥協すべき」かをロジックで制御する
Enum ConstraintPriority
cpCritical = 0 ' 絶対に変えてはいけない (Must Start/Finish)
cpNegotiable = 1 ' 平準化のために緩和可能 (Start No Earlier Than 等)
cpFlexible = 2 ' ASAP/ALAP (制約なし)
End Enum
''' <summary>
''' リソース過負荷を解消するために、制約条件を戦略的に緩和して平準化を実行する
''' </summary>
Public Sub ExecuteSmartResourceLeveling()
Dim pjApp As MSProject.Application
Set pjApp = MSProjectApplication ' 現在のインスタンスを取得
Dim t As MSProject.Task
Dim originalConstraints As Object ' 緩和前の制約を保持するための辞書(Dictionary)
Set originalConstraints = CreateObject("Scripting.Dictionary")
On Error GoTo ErrorHandler
' --- 1. パフォーマンス最適化 ---
' 計算モードを「手動」にすることで、プロパティ変更ごとの再計算によるオーバーヘッドを防ぐ
' これを怠ると、大規模プロジェクトでは処理が指数関数的に遅くなる
pjApp.Calculation = pjManual
Debug.Print "--- 平準化プロセス開始 ---"
' --- 2. 緩和フェーズ (Relaxation) ---
' 全タスクをループし、リソース過負荷かつ「緩和可能な制約」を持つタスクを特定する
For Each t In pjApp.ActiveProject.Tasks
If Not t Is Nothing Then
' リソースが過負荷であり、かつ制約が「緩和可能」なタイプか判定
If t.ResourceOverallocated And IsNegotiableConstraint(t.ConstraintType) Then
' 状態をバックアップ(後で元に戻すため、またはログ記録のため)
' Key: Task ID, Value: 元の ConstraintType
originalConstraints.Add t.ID, t.ConstraintType
' 制約を ASAP (pjTaskConstraintAsSoonAsPossible) に緩和
t.ConstraintType = pjTaskConstraintAsSoonAsPossible
Debug.Print "Task [" & t.ID & "]: 制約を緩和しました。"
End If
End If
Next t
' --- 3. 平準化実行フェーズ (Leveling) ---
Debug.Print "リソース平準化を実行中..."
pjApp.ResourceLeveling = True ' 平準化エンジンを起動
' エンジンが計算を終えるのを待つため、計算モードを自動に戻す
pjApp.Calculation = pjAutomatic
' --- 4. 事後検証フェーズ (Verification) ---
' 平準化の結果、過負荷が解消されたか、あるいは致命的な遅延が発生していないかを確認
ValidateLevelingResults pjApp, originalConstraints
MsgBox "平準化プロセスが完了しました。", vbInformation
ExitSub:
' 異常終了時でも必ず計算モードを自動に戻す(重要!)
pjApp.Calculation = pjAutomatic
Exit Sub
ErrorHandler:
MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical
Resume ExitSub
End Sub
''' <summary>
''' 緩和可能な制約タイプかどうかを判定する
''' </summary>
Private Function IsNegotiableConstraint(ByVal cType As PJConstraintType) As Boolean
Select Case cType
Case pjTaskConstraintStartNoEarlierThan, _
pjTaskConstraintFinishNoLaterThan, _
pjTaskConstraintStartNoLaterThan, _
pjTaskConstraintFinishNoEarlierThan
IsNegotiableConstraint = True
Case Else
IsNegotiableConstraint = False
End Select
End Function
''' <summary>
''' 平準化後のスケジュールを検証する
''' </summary>
Private Sub ValidateLevelingResults(ByRef pjApp As MSProject.Application, ByRef backup As Object)
Dim t As MSProject.Task
Dim overAllocatedCount As Long
overAllocatedCount = 0
For Each t In pjApp.ActiveProject.Tasks
If Not t Is Nothing Then
If t.ResourceOverallocated Then
overAllocatedCount = overAllocatedCount + 1
End If
End If
Next t
If overAllocatedCount > 0 Then
' ここで外部DBやログファイルに「緩和したが解消できなかったタスク」を記録する設計にする
Debug.Print "警告: 平準化後も " & overAllocatedCount & " 件の過負荷が残っています。"
Else
Debug.Print "成功: すべてのリソース過負荷が解消されました。"
End If
End Sub
4. 伝説のアーキテクトが教える「運用の極意」
コードを書いただけで満足してはいけない。このツールを実務の「プロダクション環境」に投入するなら、以下の3点を設計に組み込め。
① データベース連携による「変更履歴(Audit Log)」の保持
リソース平準化によって日付が変わることは、プロジェクトマネージャーにとっては「重大な変更」だ。
VBAからExcelやSQL Serverへ、「どのタスクの、どの制約を、いつ、なぜ緩和したか」を必ずログとして書き出す機能を実装せよ。これがない自動化ツールは、現場で「勝手にスケジュールを変える怪しいツール」として忌避される。
② 計算エンジンの「副作用」を恐れよ
`pjApp.Calculation = pjManual` に設定している間、オブジェクトのプロパティを変更しても、依存関係に基づく日付の更新は行われない。
「制約を緩和した直後に、そのタスクの新しい開始日を取得しようとする」ようなコードを書くと、古い(計算前の)日付が返ってくる。「変更 $\rightarrow$ 計算モードを自動に戻す $\rightarrow$ 値を取得する」というシーケンスを徹底せよ。
③ 冪等性(Idempotency)の確保
このマクロを2回、3回と連続して実行しても、プロジェクトが壊れないように設計すること。
上記のコード例では、`originalConstraints` という辞書を用いてバックアップを取る設計にしているが、より高度な設計では、計算前のプロジェクト・スナップショットを一時ファイルとして保存する手法も検討すべきだ。
結びに
リソース平準化の制御とは、単なるプログラミングではない。それは「プロジェクトの制約と現実の資源の妥協点を探る、工学的な意思決定の自動化」である。
エンジニアとして、単に「動く」コードを書く段階を卒業し、「なぜその制約を動かすのか」「その変更がプロジェクトに与える影響は何か」をコードの背後で制御できるようになれ。それが、伝説的なアーキテクトへの第一歩だ。
