<< All versions
Skill v1.0.0
currentAutomated scan100/100datadrivenconstruction/ddc_skills_for_ai_agents_in_construction/cwicr-crew-optimizer
──Details
PublishedSeptember 29, 2026 at 06:12 AM
Content Hashsha256:05220feb78273271...
Git SHAce45bbfbdd63
──Files
Files (1 file, 14.2 KB)
SKILL.md14.2 KBactive
SKILL.md · 444 lines · 14.2 KB
version: "1.0.0" name: "cwicr-crew-optimizer" description: "Optimize crew composition using CWICR labor norms. Balance productivity, cost, and skill requirements for construction crews." homepage: "https://datadrivenconstruction.io" metadata: {"openclaw": {"emoji": "🗄️", "os": ["darwin", "linux", "win32"], "homepage": "https://datadrivenconstruction.io", "requires": {"bins": ["python3"]}}}
CWICR Crew Optimizer
Business Case
Problem Statement
Crew planning challenges:
- Right mix of workers?
- Optimal crew size?
- Balance cost vs productivity?
- Match skills to work?
Solution
Optimize crew composition using CWICR labor productivity data to balance cost, output, and skill requirements.
Business Value
- Optimal productivity - Right-sized crews
- Cost efficiency - No overstaffing
- Skill matching - Proper worker mix
- Schedule support - Meet deadlines
Technical Implementation
python
import pandas as pdimport numpy as npfrom typing import Dict, Any, List, Optional, Tuplefrom dataclasses import dataclass, fieldfrom enum import Enumfrom datetime import date, timedeltaclass WorkerType(Enum):"""Types of workers."""FOREMAN = "foreman"JOURNEYMAN = "journeyman"APPRENTICE = "apprentice"LABORER = "laborer"OPERATOR = "operator"HELPER = "helper"class Trade(Enum):"""Construction trades."""CONCRETE = "concrete"CARPENTRY = "carpentry"MASONRY = "masonry"STEEL = "steel"ELECTRICAL = "electrical"PLUMBING = "plumbing"HVAC = "hvac"PAINTING = "painting"ROOFING = "roofing"GENERAL = "general"@dataclassclass Worker:"""Worker definition."""worker_type: WorkerTypetrade: Tradehourly_rate: floatproductivity_factor: float = 1.0overtime_multiplier: float = 1.5@dataclassclass CrewComposition:"""Crew composition."""name: strtrade: Tradeworkers: List[Tuple[WorkerType, int]] # (type, count)base_productivity: float # Output per hourhourly_cost: floatdaily_output: float@dataclassclass CrewOptimizationResult:"""Result of crew optimization."""work_item: strquantity: floatunit: strrecommended_crew: CrewCompositionalternative_crews: List[CrewComposition]duration_days: floattotal_labor_cost: floatcost_per_unit: float# Standard crew compositionsSTANDARD_CREWS = {'concrete_small': {'trade': Trade.CONCRETE,'workers': [(WorkerType.FOREMAN, 1), (WorkerType.JOURNEYMAN, 2), (WorkerType.LABORER, 2)],'productivity': 1.0},'concrete_large': {'trade': Trade.CONCRETE,'workers': [(WorkerType.FOREMAN, 1), (WorkerType.JOURNEYMAN, 4), (WorkerType.LABORER, 4), (WorkerType.OPERATOR, 1)],'productivity': 1.8},'masonry_standard': {'trade': Trade.MASONRY,'workers': [(WorkerType.FOREMAN, 1), (WorkerType.JOURNEYMAN, 2), (WorkerType.HELPER, 2)],'productivity': 1.0},'carpentry_framing': {'trade': Trade.CARPENTRY,'workers': [(WorkerType.FOREMAN, 1), (WorkerType.JOURNEYMAN, 3), (WorkerType.APPRENTICE, 1)],'productivity': 1.0},'electrical_rough': {'trade': Trade.ELECTRICAL,'workers': [(WorkerType.FOREMAN, 1), (WorkerType.JOURNEYMAN, 2), (WorkerType.APPRENTICE, 1)],'productivity': 1.0},'plumbing_rough': {'trade': Trade.PLUMBING,'workers': [(WorkerType.FOREMAN, 1), (WorkerType.JOURNEYMAN, 2), (WorkerType.APPRENTICE, 1)],'productivity': 1.0}}# Default hourly rates by worker typeDEFAULT_RATES = {WorkerType.FOREMAN: 65,WorkerType.JOURNEYMAN: 55,WorkerType.APPRENTICE: 35,WorkerType.LABORER: 30,WorkerType.OPERATOR: 60,WorkerType.HELPER: 28}class CWICRCrewOptimizer:"""Optimize crew composition using CWICR data."""HOURS_PER_DAY = 8def __init__(self,cwicr_data: pd.DataFrame = None,custom_rates: Dict[WorkerType, float] = None):self.cost_data = cwicr_dataself.rates = custom_rates or DEFAULT_RATESif cwicr_data is not None:self._index_data()def _index_data(self):"""Index cost data."""if 'work_item_code' in self.cost_data.columns:self._code_index = self.cost_data.set_index('work_item_code')else:self._code_index = Nonedef get_labor_norm(self, code: str) -> Tuple[float, str]:"""Get labor hours per unit from CWICR."""if self._code_index is None or code not in self._code_index.index:return (1.0, 'unit')item = self._code_index.loc[code]norm = float(item.get('labor_norm', item.get('labor_hours', 1)) or 1)unit = str(item.get('unit', 'unit'))return (norm, unit)def calculate_crew_cost(self, workers: List[Tuple[WorkerType, int]]) -> float:"""Calculate hourly cost of crew."""total = 0for worker_type, count in workers:rate = self.rates.get(worker_type, 40)total += rate * countreturn totaldef build_crew(self,name: str,trade: Trade,workers: List[Tuple[WorkerType, int]],base_productivity: float = 1.0) -> CrewComposition:"""Build crew composition."""hourly_cost = self.calculate_crew_cost(workers)daily_output = base_productivity * self.HOURS_PER_DAYreturn CrewComposition(name=name,trade=trade,workers=workers,base_productivity=base_productivity,hourly_cost=hourly_cost,daily_output=daily_output)def optimize_for_work(self,work_item_code: str,quantity: float,target_days: int = None,max_crew_size: int = 10) -> CrewOptimizationResult:"""Optimize crew for specific work item."""labor_norm, unit = self.get_labor_norm(work_item_code)total_hours = quantity * labor_norm# Detect trade from codetrade = self._detect_trade(work_item_code)# Generate crew optionscrews = []# Small crewsmall_workers = [(WorkerType.FOREMAN, 1), (WorkerType.JOURNEYMAN, 2), (WorkerType.LABORER, 1)]small_crew = self.build_crew("Small Crew", trade, small_workers, 1.0)crews.append(small_crew)# Medium crewmed_workers = [(WorkerType.FOREMAN, 1), (WorkerType.JOURNEYMAN, 3), (WorkerType.LABORER, 2)]med_crew = self.build_crew("Medium Crew", trade, med_workers, 1.4)crews.append(med_crew)# Large crewlarge_workers = [(WorkerType.FOREMAN, 1), (WorkerType.JOURNEYMAN, 5), (WorkerType.LABORER, 3)]large_crew = self.build_crew("Large Crew", trade, large_workers, 2.0)crews.append(large_crew)# Calculate metrics for each crewresults = []for crew in crews:# Adjusted productivity considering crew efficiencycrew_workers = sum(count for _, count in crew.workers)efficiency = self._crew_efficiency(crew_workers)effective_productivity = crew.base_productivity * efficiencyhours_needed = total_hours / effective_productivitydays_needed = hours_needed / self.HOURS_PER_DAYlabor_cost = hours_needed * crew.hourly_costcost_per_unit = labor_cost / quantity if quantity > 0 else 0results.append({'crew': crew,'days': days_needed,'cost': labor_cost,'cost_per_unit': cost_per_unit,'efficiency': efficiency})# Select best crew based on targetif target_days:# Find crew that meets target with lowest costvalid = [r for r in results if r['days'] <= target_days]if valid:best = min(valid, key=lambda x: x['cost'])else:best = min(results, key=lambda x: x['days'])else:# Optimize for costbest = min(results, key=lambda x: x['cost'])recommended = best['crew']alternatives = [r['crew'] for r in results if r['crew'] != recommended]return CrewOptimizationResult(work_item=work_item_code,quantity=quantity,unit=unit,recommended_crew=recommended,alternative_crews=alternatives,duration_days=round(best['days'], 1),total_labor_cost=round(best['cost'], 2),cost_per_unit=round(best['cost_per_unit'], 2))def _detect_trade(self, code: str) -> Trade:"""Detect trade from work item code."""code_lower = code.lower()trade_map = {'conc': Trade.CONCRETE,'carp': Trade.CARPENTRY,'mason': Trade.MASONRY,'steel': Trade.STEEL,'strl': Trade.STEEL,'elec': Trade.ELECTRICAL,'plumb': Trade.PLUMBING,'hvac': Trade.HVAC,'paint': Trade.PAINTING,'roof': Trade.ROOFING}for key, trade in trade_map.items():if key in code_lower:return tradereturn Trade.GENERALdef _crew_efficiency(self, crew_size: int) -> float:"""Calculate crew efficiency based on size (law of diminishing returns)."""if crew_size <= 4:return 1.0elif crew_size <= 6:return 0.95elif crew_size <= 8:return 0.90elif crew_size <= 10:return 0.85else:return 0.80def analyze_overtime(self,result: CrewOptimizationResult,available_days: int,max_overtime_hours: float = 2) -> Dict[str, Any]:"""Analyze if overtime can meet schedule."""if result.duration_days <= available_days:return {'overtime_needed': False,'regular_days': result.duration_days,'overtime_hours': 0,'overtime_cost': 0,'total_cost': result.total_labor_cost}# Calculate overtime neededregular_hours = available_days * self.HOURS_PER_DAYtotal_hours_available = available_days * (self.HOURS_PER_DAY + max_overtime_hours)labor_norm, _ = self.get_labor_norm(result.work_item)total_hours_needed = result.quantity * labor_norm / result.recommended_crew.base_productivityif total_hours_needed > total_hours_available:# Can't meet schedule even with overtimeovertime_hours = available_days * max_overtime_hoursshortage = total_hours_needed - total_hours_availableelse:overtime_hours = total_hours_needed - regular_hoursshortage = 0overtime_cost = overtime_hours * result.recommended_crew.hourly_cost * 1.5return {'overtime_needed': True,'regular_days': available_days,'overtime_hours_per_day': max_overtime_hours,'total_overtime_hours': round(overtime_hours, 1),'overtime_cost': round(overtime_cost, 2),'total_cost': round(result.total_labor_cost + overtime_cost, 2),'shortage_hours': round(shortage, 1) if shortage > 0 else 0,'can_meet_schedule': shortage == 0}def export_crew_plan(self,results: List[CrewOptimizationResult],output_path: str) -> str:"""Export crew plan to Excel."""with pd.ExcelWriter(output_path, engine='openpyxl') as writer:# Summarysummary_data = []for r in results:workers_str = ", ".join(f"{count}x {wt.value}" for wt, count in r.recommended_crew.workers)summary_data.append({'Work Item': r.work_item,'Quantity': r.quantity,'Unit': r.unit,'Crew': r.recommended_crew.name,'Workers': workers_str,'Duration Days': r.duration_days,'Labor Cost': r.total_labor_cost,'Cost/Unit': r.cost_per_unit})summary_df = pd.DataFrame(summary_data)summary_df.to_excel(writer, sheet_name='Crew Plan', index=False)# Totalstotals_df = pd.DataFrame([{'Total Duration': max(r.duration_days for r in results),'Total Labor Cost': sum(r.total_labor_cost for r in results)}])totals_df.to_excel(writer, sheet_name='Totals', index=False)return output_path
Quick Start
python
# Load CWICR datacwicr = pd.read_parquet("TR_workitems_costs_resources_DDC_CWICR.parquet")# Initialize optimizeroptimizer = CWICRCrewOptimizer(cwicr)# Optimize crew for work itemresult = optimizer.optimize_for_work(work_item_code="CONC-SLAB-001",quantity=500, # m2target_days=10)print(f"Recommended: {result.recommended_crew.name}")print(f"Duration: {result.duration_days} days")print(f"Labor Cost: ${result.total_labor_cost:,.2f}")
Common Use Cases
1. Meet Schedule with Overtime
python
overtime = optimizer.analyze_overtime(result, available_days=8)print(f"Overtime needed: {overtime['overtime_needed']}")print(f"Total cost: ${overtime['total_cost']:,.2f}")
2. Compare Crews
python
for crew in [result.recommended_crew] + result.alternative_crews:print(f"{crew.name}: ${crew.hourly_cost}/hr")
3. Custom Crew
python
custom = optimizer.build_crew(name="Custom Concrete",trade=Trade.CONCRETE,workers=[(WorkerType.FOREMAN, 1),(WorkerType.JOURNEYMAN, 4),(WorkerType.LABORER, 2)],base_productivity=1.5)
Resources
- GitHub: OpenConstructionEstimate-DDC-CWICR
- DDC Book: Chapter 3.1 - Crew Productivity Analysis