Create 4D construction simulations by linking BIM elements to project schedules. Generate time-based visualizations, sequence analysis, and construction phasing with Gantt integration.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Analyze construction schedule critical path. Identify critical activities, calculate float, simulate schedule impacts, and optimize project duration.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
설치 방법 보기설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
Analyze construction schedules using Critical Path Method (CPM). Identify critical activities, calculate total and free float, simulate what-if scenarios, and find opportunities to compress schedule.
"Understanding the critical path is essential for effective schedule management" — DDC Community
┌─────────────────────────────────────────────────────────────────┐
│ CRITICAL PATH METHOD │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Activity A (5d) ──→ Activity C (10d) ──→ Activity E (3d) │
│ ↓ ↑ │
│ Activity B (8d) ──────────→ Activity D (6d) ───┘ │
│ │
│ Critical Path: A → C → E (18 days) │
│ Float on B→D: 4 days │
│ │
└─────────────────────────────────────────────────────────────────┘
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Set, Tuple
from datetime import datetime, timedelta
from collections import defaultdict
import heapq
@dataclass
class Activity:
id: str
name: str
duration: int # days
predecessors: List[str] = field(default_factory=list)
successors: List[str] = field(default_factory=list)
# Calculated values
early_start: int = 0
early_finish: int = 0
late_start: int = 0
late_finish: int = 0
total_float: int = 0
free_float: int = 0
is_critical: bool = False
# Optional attributes
resources: List[str] = field(default_factory=list)
cost: float = 0.0
actual_start: Optional[datetime] = None
actual_finish: Optional[datetime] = None
percent_complete: float = 0.0
@dataclass
class ScheduleAnalysis:
project_duration: int
critical_path: List[str]
critical_activities: List[Activity]
near_critical_activities: List[Activity] # Float <= threshold
total_activities: int
float_distribution: Dict[int, int]
class CriticalPathAnalyzer:
"""Analyze construction schedule critical path."""
def __init__(self):
self.activities: Dict[str, Activity] = {}
self.analysis: Optional[ScheduleAnalysis] = None
def add_activity(self, id: str, name: str, duration: int,
predecessors: List[str] = None,
resources: List[str] = None,
cost: float = 0.0) -> Activity:
"""Add activity to schedule."""
activity = Activity(
id=id,
name=name,
duration=duration,
predecessors=predecessors or [],
resources=resources or [],
cost=cost
)
self.activities[id] = activity
# Update successor relationships
for pred_id in activity.predecessors:
if pred_id in self.activities:
self.activities[pred_id].successors.append(id)
return activity
def import_schedule(self, activities: List[Dict]) -> int:
"""Import activities from list of dicts."""
count = 0
for act in activities:
self.add_activity(
id=act['id'],
name=act['name'],
duration=act['duration'],
predecessors=act.get('predecessors', []),
resources=act.get('resources', []),
cost=act.get('cost', 0)
)
count += 1
return count
def calculate_critical_path(self, near_critical_threshold: int = 5) -> ScheduleAnalysis:
"""Calculate critical path using forward and backward pass."""
if not self.activities:
raise ValueError("No activities to analyze")
# Reset calculations
for act in self.activities.values():
act.early_start = 0
act.early_finish = 0
act.late_start = 0
act.late_finish = 0
act.total_float = 0
act.free_float = 0
act.is_critical = False
# Forward pass - calculate early start/finish
self._forward_pass()
# Determine project duration
project_duration = max(act.early_finish for act in self.activities.values())
# Backward pass - calculate late start/finish
self._backward_pass(project_duration)
# Calculate float and identify critical activities
critical_path = []
critical_activities = []
near_critical = []
for act in self.activities.values():
act.total_float = act.late_start - act.early_start
# Calculate free float
if act.successors:
min_succ_es = min(self.activities[s].early_start for s in act.successors)
act.free_float = min_succ_es - act.early_finish
else:
act.free_float = project_duration - act.early_finish
# Identify critical (zero float)
if act.total_float == 0:
act.is_critical = True
critical_activities.append(act)
elif act.total_float <= near_critical_threshold:
near_critical.append(act)
# Build critical path sequence
critical_path = self._build_critical_path_sequence(critical_activities)
# Float distribution
float_dist = defaultdict(int)
for act in self.activities.values():
float_dist[act.total_float] += 1
self.analysis = ScheduleAnalysis(
project_duration=project_duration,
critical_path=critical_path,
critical_activities=critical_activities,
near_critical_activities=near_critical,
total_activities=len(self.activities),
float_distribution=dict(float_dist)
)
return self.analysis
def _forward_pass(self):
"""Forward pass to calculate early start/finish."""
# Topological sort
in_degree = {act_id: len(act.predecessors) for act_id, act in self.activities.items()}
queue = [act_id for act_id, degree in in_degree.items() if degree == 0]
while queue:
act_id = queue.pop(0)
act = self.activities[act_id]
# Early start is max of predecessor early finishes
if act.predecessors:
act.early_start = max(
self.activities[pred].early_finish for pred in act.predecessors
)
else:
act.early_start = 0
act.early_finish = act.early_start + act.duration
# Process successors
for succ_id in act.successors:
in_degree[succ_id] -= 1
if in_degree[succ_id] == 0:
queue.append(succ_id)
def _backward_pass(self, project_duration: int):
"""Backward pass to calculate late start/finish."""
# Start from end activities
end_activities = [act for act in self.activities.values() if not act.successors]
for act in end_activities:
act.late_finish = project_duration
act.late_start = act.late_finish - act.duration
# Process in reverse topological order
processed = set(act.id for act in end_activities)
queue = list(end_activities)
while queue:
act = queue.pop(0)
for pred_id in act.predecessors:
pred = self.activities[pred_id]
# Late finish is min of successor late starts
new_late_finish = act.late_start
if pred.late_finish == 0 or new_late_finish < pred.late_finish:
pred.late_finish = new_late_finish
pred.late_start = pred.late_finish - pred.duration
# Check if all successors processed
all_succ_processed = all(
s in processed for s in pred.successors
)
if all_succ_processed and pred_id not in processed:
processed.add(pred_id)
queue.append(pred)
def _build_critical_path_sequence(self, critical_activities: List[Activity]) -> List[str]:
"""Build ordered critical path sequence."""
if not critical_activities:
return []
# Find start of critical path (no critical predecessors)
critical_ids = {act.id for act in critical_activities}
start_acts = [act for act in critical_activities
if not any(p in critical_ids for p in act.predecessors)]
if not start_acts:
return [act.id for act in critical_activities]
# Build sequence
path = []
current = start_acts[0]
while current:
path.append(current.id)
# Find next critical successor
critical_successors = [s for s in current.successors if s in critical_ids]
current = self.activities[critical_successors[0]] if critical_successors else None
return path
def simulate_delay(self, activity_id: str, delay_days: int) -> Dict:
"""Simulate impact of delay on an activity."""
if activity_id not in self.activities:
raise ValueError(f"Activity {activity_id} not found")
activity = self.activities[activity_id]
original_duration = activity.duration
# Temporarily modify duration
activity.duration = original_duration + delay_days
# Recalculate
new_analysis = self.calculate_critical_path()
# Restore original
activity.duration = original_duration
original_analysis = self.calculate_critical_path()
impact = {
"activity": activity_id,
"delay_days": delay_days,
"original_duration": self.analysis.project_duration,
"new_duration": new_analysis.project_duration,
"project_impact": new_analysis.project_duration - original_analysis.project_duration,
"absorbed_by_float": delay_days <= activity.total_float,
"new_critical_path": new_analysis.critical_path,
"critical_path_changed": new_analysis.critical_path != original_analysis.critical_path
}
return impact
def find_compression_opportunities(self, target_reduction: int) -> List[Dict]:
"""Find activities that could be crashed to reduce duration."""
if not self.analysis:
self.calculate_critical_path()
opportunities = []
for act in self.analysis.critical_activities:
# Assume activities can be crashed by 20% max
max_crash = int(act.duration * 0.2)
if max_crash > 0:
opportunities.append({
"activity_id": act.id,
"activity_name": act.name,
"current_duration": act.duration,
"max_crash_days": max_crash,
"crash_cost_per_day": act.cost * 0.5 / max_crash if max_crash else 0,
"on_critical_path": True
})
# Sort by crash cost
opportunities.sort(key=lambda x: x["crash_cost_per_day"])
return opportunities
def get_float_report(self) -> str:
"""Generate float analysis report."""
if not self.analysis:
self.calculate_critical_path()
lines = [
"# Float Analysis Report",
"",
f"**Project Duration:** {self.analysis.project_duration} days",
f"**Critical Activities:** {len(self.analysis.critical_activities)}",
f"**Near-Critical Activities:** {len(self.analysis.near_critical_activities)}",
"",
"## Critical Path",
"",
" → ".join(self.analysis.critical_path),
"",
"## Activities by Float",
"",
"| Activity | Duration | ES | EF | LS | LF | TF | FF | Critical |",
"|----------|----------|----|----|----|----|----|----|----------|"
]
# Sort by float
sorted_acts = sorted(self.activities.values(), key=lambda a: a.total_float)
for act in sorted_acts:
crit = "YES" if act.is_critical else ""
lines.append(
f"| {act.id} | {act.duration} | {act.early_start} | {act.early_finish} | "
f"{act.late_start} | {act.late_finish} | {act.total_float} | {act.free_float} | {crit} |"
)
return "\n".join(lines)
def generate_gantt_data(self, start_date: datetime) -> List[Dict]:
"""Generate data for Gantt chart visualization."""
gantt_data = []
for act in self.activities.values():
gantt_data.append({
"id": act.id,
"name": act.name,
"start": (start_date + timedelta(days=act.early_start)).isoformat(),
"end": (start_date + timedelta(days=act.early_finish)).isoformat(),
"duration": act.duration,
"float": act.total_float,
"is_critical": act.is_critical,
"predecessors": act.predecessors,
"progress": act.percent_complete
})
return gantt_data
# Initialize analyzer
analyzer = CriticalPathAnalyzer()
# Add activities
analyzer.add_activity("A", "Site Prep", 5)
analyzer.add_activity("B", "Foundation Excavation", 8, predecessors=["A"])
analyzer.add_activity("C", "Foundation Forms", 4, predecessors=["B"])
analyzer.add_activity("D", "Foundation Pour", 2, predecessors=["C"])
analyzer.add_activity("E", "Foundation Cure", 7, predecessors=["D"])
analyzer.add_activity("F", "Steel Erection", 15, predecessors=["E"])
analyzer.add_activity("G", "MEP Rough-in", 20, predecessors=["F"])
analyzer.add_activity("H", "Exterior Envelope", 25, predecessors=["F"])
analyzer.add_activity("I", "Interior Finishes", 30, predecessors=["G", "H"])
analyzer.add_activity("J", "Final Inspection", 3, predecessors=["I"])
# Calculate critical path
analysis = analyzer.calculate_critical_path()
print(f"Project Duration: {analysis.project_duration} days")
print(f"Critical Path: {' → '.join(analysis.critical_path)}")
print(f"Critical Activities: {len(analysis.critical_activities)}")
# Simulate delay
impact = analyzer.simulate_delay("F", 5)
print(f"\nDelay Impact: {impact['project_impact']} days")
print(f"Absorbed by Float: {impact['absorbed_by_float']}")
# Find compression opportunities
opportunities = analyzer.find_compression_opportunities(10)
for opp in opportunities[:3]:
print(f"Crash {opp['activity_name']}: up to {opp['max_crash_days']} days")
# Generate report
print(analyzer.get_float_report())
pip install (no external dependencies)
아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
Create 4D construction simulations by linking BIM elements to project schedules. Generate time-based visualizations, sequence analysis, and construction phasing with Gantt integration.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Analyze 5000+ IFC and Revit projects at scale for patterns, benchmarks, and insights. Big data analysis for construction.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Check construction AI systems against the EU AI Act (Regulation 2024/1689): classify risk of estimation, scheduling, CV and agent tools, document transparency, keep human oversight. Use when deploying or auditing AI in construction.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Orchestrate multiple AI agents for construction workflows: estimator, scheduler, document, QA and safety agents coordinated by a supervisor agent, with human checkpoints. Use when automating end-to-end project processes with agentic AI.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Create Apache Airflow DAGs for construction data pipelines. Orchestrate ETL, validation, and reporting workflows.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Automate as-built documentation and digital handover for construction. Compile project records, generate O&M manuals, create asset databases, and ensure complete project closeout.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.