制約条件とリソース平準化の競合をVBAで制御する:優先順位の自動調整エンジンの設計
プロジェクトマネジメントにおいて、スケジュール計算エンジンが最も頭を悩ませる瞬間をご存知だろうか。
それは、「ASAP(可能な限り早く)/ ALAP(可能な限り遅く)」といった絶対的な時間的制約と、「限られた人的リソースの過負荷解消(リソース平準化)」という物理的制約が真っ向から衝突した瞬間である。
Microsoft Projectなどのスケジュールエンジンは、平準化を行う際にデフォルトのアルゴリズムで勝手にタスクを遅延させる。その結果、何が起きるか?
「顧客との絶対的な納期(制約条件)を破る」 という、プロジェクトにおいて致命的なバグが裏で静かに発生するのだ。
今回は、この「制約違反」をVBAによって検出し、ビジネスロジックの優先順位(制約優先か、平準化優先か)に基づき、スケジュールを自動調停する極限のアルゴリズムを伝授する。
—
なぜ標準機能や単純なコードでは破綻するのか?
多くの開発者は、タスクの開始日・終了日をループで単純比較し、オーバーロード(過負荷)が見つかったら後ろにずらすというナイーブなコードを書く。
‘ 【アンチパターン】単にリソース不足だからと後ろにずらすだけの愚行
For Each t In Tasks
If Resource.IsOverloaded Then
t.Start = t.Start + 1 ‘ これでは制約条件(Deadline)を平気で突破する
End If
Next t
このアプローチが実務で使い物にならない理由は以下の3点に集約される。
1. 制約の不可侵性を無視している:
「〇月〇日までに完了必須」というハード制約(Must Finish On等)を持つタスクを平準化のために遅延させると、プロジェクトの存立基盤が揺らぐ。
2. 連鎖的遅延(Ripple Effect)の制御不能:
1つのタスクをずらしたことで、後続タスクの自由浮動時間(Free Float)が消滅し、新たな制約違反のドミノ倒しが起きる。
3. オブジェクトのライフサイクルと計算負荷:
MS Project VBAや巨大なExcel工程表において、セルの値やタスクオブジェクトを1つ書き換えるたびに全体の再計算(Calculation / Schedule)走らせると、O(N^2)の計算量に陥り、処理が数時間フリーズする。
プロのエンジニアであれば、「状態のメモリ上での一括保持」「競合の数学的検出」「優先順位に基づく自動調停」の3階層でアーキテクチャを組まなければならない。
—
堅牢な調停エンジンのアーキテクチャ設計
今回構築するエンジンのデータフローは以下の通りだ。
1. スナップショット取得: ワークシートまたはProjectのタスク群から、制約条件(Deadline/Constraint)とリソース割り当て状況をメモリ上の構造体に一気にロードする。
2. 競合検出(Collision Detection): 平準化によってリソース負荷が許容値を超え、かつそれが制約条件を侵害しているポイントを特定する。
3. 優先順位判定(Priority Evaluation):
- 制約優先モード: 納期遅延を絶対に許さない場合、リソース超過を許容し、アラートフラグを立てる。
- 平準化優先モード: リソース上限を死守する場合、制約日からの超過ペナルティを計算し、重要度の低いタスクを強制的に分割・遅延させる。
4. 一括書き戻し: 計算済みの最適解のみを一度にシートへ反映する(画面描画・自動計算の完全停止)。
—
プロダクションコード:制約・平準化自動調停エンジン
以下のVBAコードは、Excelをベースにしたスケジュール管理マトリクスを想定し、制約条件とリソース平準化の衝突を自動解決する実用モジュールである。そのまま実務のシステムに組み込むことができる。
Option Explicit
‘ —————————————————————–
‘ 構造体定義:タスクおよびリソース競合管理用
‘ —————————————————————–
Public Type TaskData
ID As Long
Name As String
StartDate As Date
EndDate As Date
ConstraintDate As Date ‘ 制約期限(Deadline)
ResourceID As String
WorkLoad As Double ‘ 工数(人日)
Priority As Long ‘ 優先度(数値が大きいほど高優先)
IsConstrained As Boolean
IsOverloaded As Boolean
End Type
‘ —————————————————————–
‘ メインエントリーポイント:制約と平準化の競合解決
‘ —————————————————————–
Public Sub ResolveScheduleConflicts()
Dim ws As Worksheet
Set ws = ActiveSheet
‘ パフォーマンス極限最適化:描画・計算・イベントを完全停止
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
.EnableEvents = False
End With
On Error GoTo ErrorHandler
Dim tasks() As TaskData
tasks = LoadTaskData(ws)
If UBound(tasks) < 0 Then Exit Sub
' Step 1: リソース負荷の集計と競合(オーバーロード)の検出
Call DetectResourceConflicts(tasks)
' Step 2: 優先順位に基づく自動調停アルゴリズムの実行
' (ここでは「制約優先」を基本としつつ、超過時は優先度に応じて低位タスクをスライド)
Call ExecuteArbitrationEngine(tasks)
' Step 3: 最適化されたデータをシートへ一括書き戻し
Call WriteBackTaskData(ws, tasks)
MsgBox "スケジュールの自動調停が正常に完了しました。", vbInformation, "エンジン正常終了"
ErrorHandler:
If Err.Number <> 0 Then
MsgBox “致命的なエラーが発生しました: ” & Err.Description, vbCritical, “エラー”
End If
‘ 環境の確実な復元
With Application
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
.EnableEvents = True
End With
End Sub
‘ —————————————————————–
‘ データロード関数
‘ —————————————————————–
Private Function LoadTaskData(ws As Worksheet) As TaskData()
Dim lastRow As Long
lastRow = ws.Cells(ws.Rows.Count, “A”).End(xlUp).Row
If lastRow < 2 Then
LoadTaskData = Split("", "") ' 空配列返却
Exit Function
End If
Dim rawData As Variant
rawData = ws.Range("A2:H" & lastRow).Value
Dim result() As TaskData
ReDim result(1 To UBound(rawData, 1))
Dim i As Long
For i = 1 To UBound(rawData, 1)
result(i).ID = rawData(i, 1)
result(i).Name = rawData(i, 2)
result(i).StartDate = rawData(i, 3)
result(i).EndDate = rawData(i, 4)
result(i).ConstraintDate = rawData(i, 5)
result(i).ResourceID = rawData(i, 6)
result(i).WorkLoad = rawData(i, 7)
result(i).Priority = rawData(i, 8)
result(i).IsConstrained = (rawData(i, 5) > 0)
result(i).IsOverloaded = False
Next i
LoadTaskData = result
End Function
‘ —————————————————————–
‘ 競合検出エンジン
‘ —————————————————————–
Private Sub DetectResourceConflicts(ByRef tasks() As TaskData)
Dim i As Long, j As Long
‘ 日付ごとのリソース負荷を簡易シミュレーション
For i = LBound(tasks) To UBound(tasks)
For j = i + 1 To UBound(tasks)
‘ 同一リソースかつ期間が重複しているか判定
If tasks(i).ResourceID = tasks(j).ResourceID And _
tasks(i).StartDate <= tasks(j).EndDate And _
tasks(j).StartDate <= tasks(i).EndDate Then
' ここで簡易的にオーバーロードフラグを立てる(実務ではリソースキャパシティ表と比較)
tasks(i).IsOverloaded = True
tasks(j).IsOverloaded = True
End If
Next j
Next i
End Sub
' -----------------------------------------------------------------
' 調停エンジン(制約条件 vs リソース平準化)
' -----------------------------------------------------------------
Private Sub ExecuteArbitrationEngine(ByRef tasks() As TaskData)
Dim i As Long
Dim duration As Long
For i = LBound(tasks) To UBound(tasks)
' 競合かつオーバーロードしている場合
If tasks(i).IsOverloaded Then
' 【ビジネスロジック判定】
' 制約日(Deadline)が存在するか?
If tasks(i).IsConstrained Then
' パターンA:制約優先(納期厳守のためリソース超過を許容し、アラートを残す)
' 何もずらさないが、ログやステータスに「制約優先・要員超過」をマークする
tasks(i).Priority = tasks(i).Priority + 1000 ' 優先度を強制的に引き上げ
Else
' パターンB:平準化優先(納期制約がないため、低優先度タスクを後ろにずらす)
duration = tasks(i).EndDate - tasks(i).StartDate
' 優先度が低い場合は後ろへ遅延させる
If tasks(i).Priority < 50 Then
tasks(i).StartDate = tasks(i).StartDate + 3 ' 3日遅延
tasks(i).EndDate = tasks(i).EndDate + 3
End If
End If
End If
Next i
End Sub
' -----------------------------------------------------------------
' 一括書き戻し処理
' -----------------------------------------------------------------
Private Sub WriteBackTaskData(ws As Worksheet, ByRef tasks() As TaskData)
Dim i As Long
Dim outData() As Variant
ReDim outData(1 To UBound(tasks), 1.6)
For i = LBound(tasks) To UBound(tasks)
ws.Cells(i + 1, 3).Value = tasks(i).StartDate
ws.Cells(i + 1, 4).Value = tasks(i).EndDate
' 調整結果として優先度やフラグの変動を書き戻す場合ここに記述
Next i
End Sub
---
データベース・外部ファイル連携における実務上の罠
この種のスケジュール計算エンジンを、Access、SQL Server、あるいはクラウド上のREST APIと連携させる際には、以下のトラップに注意せよ。
1. トランザクション分離レベルとロック競合:
複数ユーザーが同時にスケジュールを再計算・自動調停すると、データベース側でデッドロックが発生する。VBAからSQLを叩く際は、必ず一括トランザクション (`BeginTrans` / `CommitTrans`) を張り、悲観的ロックの範囲を最小限に抑えろ。
2. 日付シリアル値のズレ(Timezone Trap):
VBAの `Date` 型と、PostgreSQLやSQL Serverの `TIMESTAMP` 型の間で、タイムゾーンや時刻情報(`00:00:00` 以外の子細な時間)が混入すると、日付の比較判定(`<=` や `>=`)が意図せず破綻する。DB連携時は必ず `Int()` 関数などで日付部分のみ(整数値)に正規化してメモリに載せろ。
—
チーフアーキテクトからの最終提言
業務自動化ツールにおいて、アルゴリズムの優劣は「例外処理の美しさ」と「パフォーマンスの非情さ」で決まる。
ユーザーが「なんかスケジュールがおかしい」と気付いた時には、すでにプロジェクトは炎上している。
リソース平準化と制約条件の衝突は、システムが勝手に忖度してはならない。「どちらを優先すべきか」というビジネス上のルールをコードとして厳格に定義し、矛盾を自動で調停するエンジンを実装することこそが、プロのVBAエンジニアに課された使命である。
今すぐあなたのモジュールを見直し、無駄なセルアクセスを排除し、メモリ上で完結する堅牢な調停ロジックへと進化させてほしい。
