Create 4D construction simulations by linking BIM elements to project schedules. Generate time-based visualizations, sequence analysis, and construction phasing with Gantt integration.
npx skills add https://github.com/datadrivenconstruction/DDC_Skills_for_AI_Agents_in_Construction --skill 4d-simulation
Based on DDC methodology (Chapter 3.3), this skill implements 4D BIM simulations - linking 3D model elements to the 4th dimension: time. Visualize construction sequences, detect scheduling conflicts, and optimize work phasing.
Book Reference: "4D, 6D-8D и расчет CO2" / "4D-8D BIM and CO2 Calculation"
> "4D моделирование позволяет визуализировать последовательность строительства и выявлять конфликты на этапе планирования."
> — DDC Book, Chapter 3.3
import pandas as pd
from datetime import datetime, timedelta
# BIM elements with schedule data
elements = pd.DataFrame({
'ElementId': ['E001', 'E002', 'E003', 'E004'],
'Category': ['Foundation', 'Column', 'Beam', 'Slab'],
'Level': ['Level 0', 'Level 1', 'Level 1', 'Level 1'],
'Start_Date': ['2024-01-01', '2024-01-15', '2024-02-01', '2024-02-15'],
'End_Date': ['2024-01-14', '2024-01-31', '2024-02-14', '2024-02-28'],
'Phase': ['Structure', 'Structure', 'Structure', 'Structure']
})
elements['Start_Date'] = pd.to_datetime(elements['Start_Date'])
elements['End_Date'] = pd.to_datetime(elements['End_Date'])
elements['Duration_Days'] = (elements['End_Date'] - elements['Start_Date']).dt.days
# Get elements active on a specific date
target_date = pd.to_datetime('2024-01-20')
active_elements = elements[
(elements['Start_Date'] <= target_date) &
(elements['End_Date'] >= target_date)
]
print(f"Elements under construction on {target_date.date()}:")
print(active_elements[['ElementId', 'Category']])
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from typing import List, Dict, Optional
class ScheduleElementLinker:
"""Link BIM elements to schedule activities"""
def __init__(self, elements_df: pd.DataFrame, schedule_df: pd.DataFrame):
self.elements = elements_df.copy()
self.schedule = schedule_df.copy()
self.links = pd.DataFrame()
def auto_link_by_category(self, mapping: Dict[str, str]):
"""Auto-link elements to activities by category mapping
Args:
mapping: Dict mapping element categories to activity names
e.g., {'Wall': 'Structural Walls', 'Slab': 'Floor Construction'}
"""
links = []
for category, activity_name in mapping.items():
# Find elements of this category
category_elements = self.elements[
self.elements['Category'] == category
]['ElementId'].tolist()
# Find matching activity
activity = self.schedule[
self.schedule['Activity'].str.contains(activity_name, case=False)
]
if not activity.empty and category_elements:
for elem_id in category_elements:
links.append({
'ElementId': elem_id,
'ActivityId': activity.iloc[0]['ActivityId'],
'Activity': activity.iloc[0]['Activity'],
'Start_Date': activity.iloc[0]['Start_Date'],
'End_Date': activity.iloc[0]['End_Date']
})
self.links = pd.DataFrame(links)
return self.links
def auto_link_by_level(self):
"""Auto-link elements based on level and construction sequence"""
# Get unique levels in order
levels = sorted(self.elements['Level'].unique())
links = []
for i, level in enumerate(levels):
level_elements = self.elements[self.elements['Level'] == level]
# Find activity for this level
level_activity = self.schedule[
self.schedule['Activity'].str.contains(level, case=False)
]
if not level_activity.empty:
for _, elem in level_elements.iterrows():
links.append({
'ElementId': elem['ElementId'],
'ActivityId': level_activity.iloc[0]['ActivityId'],
'Activity': level_activity.iloc[0]['Activity'],
'Start_Date': level_activity.iloc[0]['Start_Date'],
'End_Date': level_activity.iloc[0]['End_Date']
})
self.links = pd.DataFrame(links)
return self.links
def manual_link(self, element_id: str, activity_id: str):
"""Manually link element to activity"""
element = self.elements[self.elements['ElementId'] == element_id]
activity = self.schedule[self.schedule['ActivityId'] == activity_id]
if element.empty or activity.empty:
raise ValueError("Element or activity not found")
new_link = pd.DataFrame([{
'ElementId': element_id,
'ActivityId': activity_id,
'Activity': activity.iloc[0]['Activity'],
'Start_Date': activity.iloc[0]['Start_Date'],
'End_Date': activity.iloc[0]['End_Date']
}])
self.links = pd.concat([self.links, new_link], ignore_index=True)
return self.links
def get_linked_elements(self):
"""Get elements with schedule data"""
return self.elements.merge(
self.links[['ElementId', 'ActivityId', 'Start_Date', 'End_Date']],
on='ElementId',
how='left'
)
class Simulation4D:
"""4D construction simulation engine"""
def __init__(self, linked_elements: pd.DataFrame):
self.elements = linked_elements.copy()
self.elements['Start_Date'] = pd.to_datetime(self.elements['Start_Date'])
self.elements['End_Date'] = pd.to_datetime(self.elements['End_Date'])
self.project_start = self.elements['Start_Date'].min()
self.project_end = self.elements['End_Date'].max()
def get_state_at_date(self, target_date: datetime) -> pd.DataFrame:
"""Get element states at a specific date"""
target = pd.to_datetime(target_date)
# Determine state for each element
conditions = [
target < self.elements['Start_Date'], # Not started
(self.elements['Start_Date'] <= target) & (target <= self.elements['End_Date']), # In progress
target > self.elements['End_Date'] # Completed
]
choices = ['not_started', 'in_progress', 'completed']
self.elements['State'] = np.select(conditions, choices, default='unknown')
return self.elements.copy()
def generate_timeline(self, interval_days: int = 7) -> List[Dict]:
"""Generate timeline snapshots"""
timeline = []
current_date = self.project_start
while current_date <= self.project_end:
state = self.get_state_at_date(current_date)
snapshot = {
'date': current_date,
'not_started': len(state[state['State'] == 'not_started']),
'in_progress': len(state[state['State'] == 'in_progress']),
'completed': len(state[state['State'] == 'completed']),
'total': len(state)
}
snapshot['progress_pct'] = (snapshot['completed'] / snapshot['total']) * 100
timeline.append(snapshot)
current_date += timedelta(days=interval_days)
return timeline
def get_elements_in_progress(self, target_date: datetime) -> pd.DataFrame:
"""Get elements currently under construction"""
state = self.get_state_at_date(target_date)
return state[state['State'] == 'in_progress']
def analyze_construction_sequence(self) -> pd.DataFrame:
"""Analyze construction sequence by category and level"""
sequence = self.elements.groupby(['Level', 'Category']).agg({
'Start_Date': 'min',
'End_Date': 'max',
'ElementId': 'count'
}).rename(columns={'ElementId': 'Element_Count'})
sequence['Duration_Days'] = (sequence['End_Date'] - sequence['Start_Date']).dt.days
sequence = sequence.sort_values('Start_Date').reset_index()
return sequence
def detect_parallel_work(self) -> pd.DataFrame:
"""Detect work happening in parallel"""
dates = pd.date_range(self.project_start, self.project_end, freq='D')
parallel_work = []
for date in dates:
state = self.get_state_at_date(date)
in_progress = state[state['State'] == 'in_progress']
if len(in_progress) > 1:
categories = in_progress['Category'].unique().tolist()
levels = in_progress['Level'].unique().tolist()
parallel_work.append({
'date': date,
'parallel_count': len(in_progress),
'categories': ', '.join(categories),
'levels': ', '.join(levels)
})
return pd.DataFrame(parallel_work)
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib.patches import Patch
class GanttChartGenerator:
"""Generate Gantt charts for 4D simulation"""
def __init__(self, elements: pd.DataFrame):
self.elements = elements.copy()
self.colors = {
'Foundation': '#8B4513',
'Column': '#4169E1',
'Beam': '#228B22',
'Slab': '#DC143C',
'Wall': '#FF8C00',
'Roof': '#9932CC',
'MEP': '#20B2AA',
'Finishes': '#FFD700'
}
def create_gantt(self, group_by='Category', figsize=(14, 8)):
"""Create Gantt chart grouped by specified column"""
fig, ax = plt.subplots(figsize=figsize)
# Group elements
groups = self.elements.groupby(group_by)
y_pos = 0
y_labels = []
legend_elements = []
for group_name, group_df in groups:
color = self.colors.get(group_name, '#808080')
for _, row in group_df.iterrows():
start = row['Start_Date']
duration = (row['End_Date'] - row['Start_Date']).days
ax.barh(y_pos, duration, left=start, height=0.6,
color=color, alpha=0.8, edgecolor='black', linewidth=0.5)
y_pos += 1
y_labels.append(group_name)
legend_elements.append(Patch(facecolor=color, label=group_name))
# Formatting
ax.set_yticks(range(len(self.elements)))
ax.set_yticklabels(self.elements['ElementId'])
ax.set_xlabel('Date')
ax.set_title('Construction Schedule - Gantt Chart', fontsize=14, fontweight='bold')
# Date formatting
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
ax.xaxis.set_major_locator(mdates.WeekdayLocator(interval=2))
plt.xticks(rotation=45)
# Legend
ax.legend(handles=legend_elements, loc='upper right')
# Grid
ax.grid(axis='x', alpha=0.3)
plt.tight_layout()
return fig
def create_level_gantt(self, figsize=(14, 10)):
"""Create Gantt chart grouped by level"""
fig, ax = plt.subplots(figsize=figsize)
levels = sorted(self.elements['Level'].unique())
for i, level in enumerate(levels):
level_elements = self.elements[self.elements['Level'] == level]
for _, row in level_elements.iterrows():
color = self.colors.get(row['Category'], '#808080')
start = row['Start_Date']
duration = (row['End_Date'] - row['Start_Date']).days
ax.barh(i, duration, left=start, height=0.4,
color=color, alpha=0.8, edgecolor='black', linewidth=0.5)
ax.set_yticks(range(len(levels)))
ax.set_yticklabels(levels)
ax.set_xlabel('Date')
ax.set_ylabel('Building Level')
ax.set_title('Construction Sequence by Level', fontsize=14, fontweight='bold')
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
plt.xticks(rotation=45)
# Add legend
legend_patches = [Patch(color=c, label=cat) for cat, c in self.colors.items()
if cat in self.elements['Category'].values]
ax.legend(handles=legend_patches, loc='upper right')
plt.tight_layout()
return fig
def create_progress_chart(self, timeline: List[Dict], figsize=(12, 6)):
"""Create S-curve progress chart"""
df = pd.DataFrame(timeline)
fig, ax = plt.subplots(figsize=figsize)
ax.plot(df['date'], df['progress_pct'], 'b-', linewidth=2, label='Progress')
ax.fill_between(df['date'], 0, df['progress_pct'], alpha=0.3)
# Add milestones at 25%, 50%, 75%, 100%
for milestone in [25, 50, 75, 100]:
ax.axhline(y=milestone, color='gray', linestyle='--', alpha=0.5)
ax.text(df['date'].iloc[0], milestone + 2, f'{milestone}%',
fontsize=9, color='gray')
ax.set_xlabel('Date')
ax.set_ylabel('Progress (%)')
ax.set_title('Project S-Curve', fontsize=14, fontweight='bold')
ax.set_ylim(0, 105)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
plt.xticks(rotation=45)
plt.tight_layout()
return fig
class SequenceAnalyzer:
"""Analyze and optimize construction sequences"""
def __init__(self, elements: pd.DataFrame):
self.elements = elements.copy()
self.dependencies = []
def add_dependency(self, predecessor: str, successor: str, lag_days: int = 0):
"""Add dependency between elements"""
self.dependencies.append({
'predecessor': predecessor,
'successor': successor,
'lag_days': lag_days
})
def check_sequence_violations(self) -> List[Dict]:
"""Check for sequence violations"""
violations = []
for dep in self.dependencies:
pred = self.elements[self.elements['ElementId'] == dep['predecessor']]
succ = self.elements[self.elements['ElementId'] == dep['successor']]
if pred.empty or succ.empty:
continue
pred_end = pred.iloc[0]['End_Date']
succ_start = succ.iloc[0]['Start_Date']
required_start = pred_end + timedelta(days=dep['lag_days'])
if succ_start < required_start:
violations.append({
'predecessor': dep['predecessor'],
'successor': dep['successor'],
'predecessor_end': pred_end,
'successor_start': succ_start,
'required_start': required_start,
'violation_days': (required_start - succ_start).days
})
return violations
def check_spatial_conflicts(self) -> List[Dict]:
"""Check for spatial conflicts (same location, same time)"""
conflicts = []
# Group by location (Level + Zone if available)
location_col = 'Level'
for location, group in self.elements.groupby(location_col):
if len(group) < 2:
continue
# Check for overlapping work
for i, row1 in group.iterrows():
for j, row2 in group.iterrows():
if i >= j:
continue
# Check date overlap
overlap = (row1['Start_Date'] <= row2['End_Date'] and
row2['Start_Date'] <= row1['End_Date'])
if overlap:
conflicts.append({
'location': location,
'element1': row1['ElementId'],
'element1_category': row1['Category'],
'element2': row2['ElementId'],
'element2_category': row2['Category'],
'overlap_start': max(row1['Start_Date'], row2['Start_Date']),
'overlap_end': min(row1['End_Date'], row2['End_Date'])
})
return conflicts
def calculate_critical_path(self) -> List[str]:
"""Calculate critical path (simplified)"""
# Build dependency graph
graph = {}
for elem in self.elements['ElementId']:
graph[elem] = {
'predecessors': [],
'duration': 0
}
for dep in self.dependencies:
if dep['successor'] in graph:
graph[dep['successor']]['predecessors'].append(dep['predecessor'])
# Calculate durations
for _, row in self.elements.iterrows():
if row['ElementId'] in graph:
graph[row['ElementId']]['duration'] = (row['End_Date'] - row['Start_Date']).days
# Find longest path (simplified critical path)
def longest_path(node, memo={}):
if node in memo:
return memo[node]
if not graph[node]['predecessors']:
return graph[node]['duration']
max_pred = max(
longest_path(pred, memo) for pred in graph[node]['predecessors']
)
memo[node] = max_pred + graph[node]['duration']
return memo[node]
# Get all path lengths
path_lengths = {elem: longest_path(elem) for elem in graph.keys()}
# Critical path elements
max_length = max(path_lengths.values())
critical = [elem for elem, length in path_lengths.items() if length == max_length]
return critical
def export_4d_schedule(elements: pd.DataFrame, output_path: str):
"""Export 4D schedule to Excel with multiple views"""
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
# Full schedule
elements.to_excel(writer, sheet_name='Schedule', index=False)
# By level
level_summary = elements.groupby('Level').agg({
'ElementId': 'count',
'Start_Date': 'min',
'End_Date': 'max'
}).rename(columns={'ElementId': 'Element_Count'})
level_summary['Duration_Days'] = (level_summary['End_Date'] - level_summary['Start_Date']).dt.days
level_summary.to_excel(writer, sheet_name='By_Level')
# By category
cat_summary = elements.groupby('Category').agg({
'ElementId': 'count',
'Start_Date': 'min',
'End_Date': 'max'
}).rename(columns={'ElementId': 'Element_Count'})
cat_summary.to_excel(writer, sheet_name='By_Category')
return output_path
| Concept | Description |
|---------|-------------|
| 4D = 3D + Time | BIM model linked to schedule |
| Activity | Scheduled work item |
| Element State | not_started / in_progress / completed |
| Critical Path | Longest sequence determining project duration |
gantt-chart for schedule visualizationco2-estimation for 6D (sustainability) analysisclash-detection-analysis for 4D conflict detectionExpert startup business analyst specializing in market sizing, financial modeling, competitive analysis, and strategic planning for early-stage companies. Use PROACTIVELY when the user asks about market opportunity, TAM/SAM/SOM, financial projections, unit economics, competitive landscape, team planning, startup metrics, or business strategy for pre-seed through Series A startups.
This skill should be used when the user asks to "plan team structure", "determine hiring needs", "design org chart", "calculate compensation", "plan equity allocation", or requests organizational design and headcount planning for a startup.
End-to-end bulk RNA-seq orchestrator — takes raw FASTQ reads through QC and trimming (FastQC, fastp/Trim Galore), alignment and quantification (STAR, Salmon, featureCounts), assembles a gene-level counts matrix, then hands off to differential expression (pydeseq2), pathway/GSEA enrichment (pathway-enrichment), and publication figures (scientific-visualization). Use whenever the user has bulk RNA-seq reads or quant output and wants a complete, reproducible differential-expression workflow — e.g. "analyze my RNA-seq", "FASTQ to DESeq2", "run nf-core/rnaseq", "STAR/Salmon quantification", "build a counts matrix for DESeq2", or "go from reads to differentially expressed genes and enriched pathways". Routes between an nf-core/rnaseq (Nextflow) path and a standalone STAR/Salmon path, and covers experimental design, strandedness, and QC gates. For single-cell RNA-seq use the scanpy skill instead.
Generate project status reports from Jira issues and publish to Confluence. When an agent needs to: (1) Create a status report for a project, (2) Summarize project progress or updates, (3) Generate weekly/daily reports from Jira, (4) Publish status summaries to Confluence, or (5) Analyze project blockers and completion. Queries Jira issues, categorizes by status/priority, and creates formatted reports for delivery managers and executives.
Evaluates market bubble risk through quantitative data-driven analysis using the revised Minsky/Kindleberger framework v2.1. Prioritizes objective metrics (Put/Call, VIX, margin debt, breadth, IPO data) over subjective impressions. Features strict qualitative adjustment criteria with confirmation bias prevention. Supports practical investment decisions with mandatory data collection and mechanical scoring. Use when user asks about bubble risk, valuation concerns, or profit-taking timing.
Google Workflow: Today's meetings + open tasks as a standup summary.
Read event data from a Google Sheets spreadsheet and create Google Calendar entries for each row.
Create professional, dark-themed SVG diagrams of any type — architecture diagrams, flowcharts, sequence diagrams, structural diagrams, mind maps, timelines, illustrative/conceptual diagrams, and more. Use this skill whenever the user asks for any kind of technical or conceptual diagram, visualization of a system, process flow, data flow, component relationship, network topology, decision tree, org chart, state machine, or any visual representation of structure/logic/process. Also trigger when the user says "画个图" "画一个架构图" "diagram" "flowchart" "sequence diagram" "draw me a ..." or uploads content and asks to visualize it. Output is always a standalone .svg file.
Take datadrivenconstruction/4d-simulation from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.