| name | capacity-planning |
| description | Plan organizational capacity for construction projects. Forecast resource needs, identify capacity gaps, and support strategic planning for project pursuit and staffing. |
| homepage | https://datadrivenconstruction.io |
| metadata | {"openclaw":{"emoji":"🚀","os":["darwin","linux","win32"],"homepage":"https://datadrivenconstruction.io","requires":{"bins":"[Truncated]"}}} |
Capacity Planning
Overview
Strategic capacity planning for construction organizations. Forecast resource requirements based on project pipeline, identify capacity constraints, optimize staffing levels, and support go/no-go decisions on new project pursuits.
Capacity Planning Framework
┌─────────────────────────────────────────────────────────────────┐
│ CAPACITY PLANNING │
├─────────────────────────────────────────────────────────────────┤
│ │
│ DEMAND FORECAST CAPACITY ANALYSIS DECISIONS │
│ ─────────────── ───────────────── ───────── │
│ │
│ Current Projects → Available: Pursue new │
│ • Project A (Active) 👷 PM: 5 project? │
│ • Project B (Active) 👷 Supers: 12 ──────── │
│ • Project C (Starting) 📐 Engineers: 8 ✅ Capacity │
│ ⚠️ Stretch │
│ Pipeline: → Required: ❌ Decline │
│ • Bid D (60% win) 👷 PM: 7 │
│ • Bid E (40% win) 👷 Supers: 15 │
│ • Opportunity F 📐 Engineers: 10 │
│ │
│ GAP ANALYSIS: ACTIONS: │
│ • PM: -2 (deficit) • Hire 2 PMs │
│ • Supers: -3 (deficit) • Promote from within │
│ • Engineers: -2 (deficit) • Partner with firm │
│ │
└─────────────────────────────────────────────────────────────────┘
Technical Implementation
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple
from datetime import datetime, timedelta
from enum import Enum
import statistics
class ResourceRole(Enum):
PROJECT_MANAGER = "project_manager"
SUPERINTENDENT = "superintendent"
PROJECT_ENGINEER = "project_engineer"
ESTIMATOR = "estimator"
SCHEDULER = "scheduler"
SAFETY_MANAGER = "safety_manager"
QC_MANAGER = "qc_manager"
ADMIN = "admin"
class ProjectPhase(Enum):
PURSUIT = "pursuit"
PRECONSTRUCTION = "preconstruction"
CONSTRUCTION = "construction"
CLOSEOUT = "closeout"
class OpportunityStatus(Enum):
IDENTIFIED = "identified"
PURSUING = "pursuing"
BID_SUBMITTED = "bid_submitted"
NEGOTIATING = "negotiating"
WON = "won"
LOST = "lost"
@dataclass
class StaffMember:
id: str
name: str
role: ResourceRole
capacity: float =
current_assignment: =
availability_date: datetime =
skills: [] = field(default_factory=)
max_project_value: =
:
project_id:
project_name:
value:
phase: ProjectPhase
start_date: datetime
end_date: datetime
probability: =
resource_needs: [ResourceRole, ] = field(default_factory=)
:
role: ResourceRole
period_start: datetime
period_end: datetime
demand:
capacity:
gap:
severity:
:
forecast_date: datetime
horizon_months:
total_demand_fte:
total_capacity_fte:
utilization_pct:
gaps: [CapacityGap]
recommendations: []
:
STAFFING_RATIOS = {
ResourceRole.PROJECT_MANAGER: ,
ResourceRole.SUPERINTENDENT: ,
ResourceRole.PROJECT_ENGINEER: ,
ResourceRole.ESTIMATOR: ,
ResourceRole.SCHEDULER: ,
ResourceRole.SAFETY_MANAGER: ,
}
PHASE_FACTORS = {
ProjectPhase.PURSUIT: {: , : },
ProjectPhase.PRECONSTRUCTION: {: , : , : },
ProjectPhase.CONSTRUCTION: {: , : , : , : },
ProjectPhase.CLOSEOUT: {: , : , : }
}
():
.organization_name = organization_name
.staff: [, StaffMember] = {}
.projects: [, ProjectDemand] = {}
.pipeline: [, ProjectDemand] = {}
() -> StaffMember:
member = StaffMember(
=,
name=name,
role=role,
capacity=capacity,
current_assignment=current_assignment,
availability_date=availability_date datetime.now(),
max_project_value=max_project_value
)
.staff[] = member
member
() -> ProjectDemand:
needs = ._calculate_resource_needs(value, phase)
project = ProjectDemand(
project_id=,
project_name=name,
value=value,
phase=phase,
start_date=start_date,
end_date=end_date,
probability=,
resource_needs=needs
)
.projects[] = project
project
() -> ProjectDemand:
needs = ._calculate_resource_needs(value, ProjectPhase.CONSTRUCTION)
opportunity = ProjectDemand(
project_id=,
project_name=name,
value=value,
phase=ProjectPhase.PURSUIT,
start_date=expected_start,
end_date=expected_start + timedelta(days=duration_months * ),
probability=win_probability,
resource_needs=needs
)
.pipeline[] = opportunity
opportunity
() -> [ResourceRole, ]:
needs = {}
role, ratio .STAFFING_RATIOS.items():
base_need = value / ratio
phase_key = role.value.split()[][:]
factor =
phase .PHASE_FACTORS:
factor = .PHASE_FACTORS[phase].get(phase_key, )
needs[role] = base_need * factor
needs
() -> [ResourceRole, ]:
capacity = {role: role ResourceRole}
member .staff.values():
member.availability_date <= datetime.now():
capacity[member.role] += member.capacity
capacity
() -> [ResourceRole, ]:
capacity = {role: role ResourceRole}
member .staff.values():
member.availability_date <= target_date:
capacity[member.role] += member.capacity
capacity
() -> [ResourceRole, ]:
demand = {role: role ResourceRole}
project .projects.values():
project.start_date <= target_date <= project.end_date:
role, need project.resource_needs.items():
demand[role] += need * project.probability
include_pipeline:
opp .pipeline.values():
opp.probability >= pipeline_threshold:
opp.start_date <= target_date <= opp.end_date:
role, need opp.resource_needs.items():
demand[role] += need * opp.probability
demand
() -> [CapacityGap]:
gaps = []
month (horizon_months):
period_start = datetime.now() + timedelta(days=month * )
period_end = period_start + timedelta(days=)
capacity = .get_capacity_at_date(period_start)
demand = .calculate_demand(period_start, include_pipeline=)
role ResourceRole:
cap = capacity.get(role, )
dem = demand.get(role, )
gap = cap - dem
gap < :
severity = gap < -
gaps.append(CapacityGap(
role=role,
period_start=period_start,
period_end=period_end,
demand=dem,
capacity=cap,
gap=gap,
severity=severity
))
gaps
() -> :
needs = ._calculate_resource_needs(value, ProjectPhase.CONSTRUCTION)
end_date = start_date + timedelta(days=duration_months * )
can_staff =
bottlenecks = []
current_date = start_date
current_date <= end_date:
capacity = .get_capacity_at_date(current_date)
demand = .calculate_demand(current_date)
role, need needs.items():
available = capacity.get(role, ) - demand.get(role, )
need > available:
can_staff =
bottlenecks.append({
: current_date,
: role.value,
: need,
: available,
: need - available
})
current_date += timedelta(days=)
can_staff:
recommendation =
(bottlenecks) <= :
recommendation =
:
recommendation =
{
: can_staff,
: recommendation,
: {r.value: v r, v needs.items()},
: bottlenecks[:],
: ._suggest_hiring(bottlenecks)
}
() -> []:
bottlenecks:
[]
role_gaps = {}
b bottlenecks:
role = b[]
role role_gaps:
role_gaps[role] =
role_gaps[role] = (role_gaps[role], b[])
actions = []
role, gap (role_gaps.items(), key= x: -x[]):
hires = (gap) +
actions.append()
actions
() -> CapacityForecast:
gaps = .identify_gaps(horizon_months)
capacity = .get_current_capacity()
demand = .calculate_demand(datetime.now())
total_capacity = (capacity.values())
total_demand = (demand.values())
utilization = (total_demand / total_capacity * ) total_capacity >
recommendations = []
utilization > :
recommendations.append()
utilization < :
recommendations.append()
critical_gaps = [g g gaps g.severity == ]
gap_roles = (g.role.value g critical_gaps)
role gap_roles:
recommendations.append()
CapacityForecast(
forecast_date=datetime.now(),
horizon_months=horizon_months,
total_demand_fte=total_demand,
total_capacity_fte=total_capacity,
utilization_pct=utilization,
gaps=gaps,
recommendations=recommendations
)
() -> :
forecast = .generate_forecast()
lines = [
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
]
capacity = .get_current_capacity()
demand = .calculate_demand(datetime.now())
role ResourceRole:
cap = capacity.get(role, )
dem = demand.get(role, )
gap = cap - dem
gap_icon = gap >= gap > -
lines.append(
)
lines.extend([
,
,
,
,
])
p (.projects.values(), key= x: x.value, reverse=):
lines.append(
)
.pipeline:
lines.extend([
,
,
,
,
])
p (.pipeline.values(), key= x: -x.probability):
lines.append(
)
critical_gaps = [g g forecast.gaps g.severity == ]
critical_gaps:
lines.extend([
,
,
,
,
])
gap critical_gaps[:]:
lines.append(
)
forecast.recommendations:
lines.extend([
,
,
])
rec forecast.recommendations:
lines.append()
.join(lines)
Quick Start
from datetime import datetime, timedelta
planner = CapacityPlanner("ABC Construction")
planner.add_staff("PM-001", "John Smith", ResourceRole.PROJECT_MANAGER)
planner.add_staff("PM-002", "Jane Doe", ResourceRole.PROJECT_MANAGER)
planner.add_staff("SUP-001", "Mike Johnson", ResourceRole.SUPERINTENDENT)
planner.add_staff("SUP-002", "Bob Williams", ResourceRole.SUPERINTENDENT)
planner.add_staff("SUP-003", "Tom Brown", ResourceRole.SUPERINTENDENT)
planner.add_staff("PE-001", "Sarah Davis", ResourceRole.PROJECT_ENGINEER)
planner.add_staff("PE-002", "Chris Wilson", ResourceRole.PROJECT_ENGINEER)
planner.add_active_project(
"PRJ-001", "Downtown Tower",
value=25000000,
phase=ProjectPhase.CONSTRUCTION,
start_date=datetime(2024, 6, 1),
end_date=datetime(2025, 12, 31)
)
planner.add_active_project(
"PRJ-002", "Hospital Wing",
value=40000000,
phase=ProjectPhase.CONSTRUCTION,
start_date=datetime(2024, 9, 1),
end_date=datetime(2026, 6, 30)
)
planner.add_pipeline_opportunity(
"OPP-001", "Office Complex",
value=30000000,
win_probability=,
expected_start=datetime(, , ),
duration_months=
)
evaluation = planner.can_pursue_project(
value=,
start_date=datetime(, , ),
duration_months=
)
()
action evaluation[]:
()
forecast = planner.generate_forecast()
()
()
(planner.generate_report())
Requirements
pip install (no external dependencies)