DDC_Skills_for_AI_Agents_in_Construction weather-impact-analysis
Analyze weather data impact on construction schedules. Predict weather delays, optimize work scheduling based on forecasts, and calculate weather-related risk factors for project planning.
install
source · Clone the upstream repo
git clone https://github.com/datadrivenconstruction/DDC_Skills_for_AI_Agents_in_Construction
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/datadrivenconstruction/DDC_Skills_for_AI_Agents_in_Construction "$T" && mkdir -p ~/.claude/skills && cp -r "$T/5_DDC_Innovative/weather-impact-analysis" ~/.claude/skills/datadrivenconstruction-ddc-skills-for-ai-agents-in-construction-weather-impact-a && rm -rf "$T"
manifest:
5_DDC_Innovative/weather-impact-analysis/SKILL.mdsource content
Weather Impact Analysis
Overview
This skill implements weather data analysis for construction project management. Integrate weather forecasts, historical data, and activity sensitivity to predict delays and optimize scheduling.
Capabilities:
- Weather forecast integration
- Activity weather sensitivity mapping
- Delay prediction and quantification
- Schedule optimization based on weather
- Historical weather impact analysis
- Risk factor calculation
Quick Start
from dataclasses import dataclass from datetime import date, datetime, timedelta from typing import List, Dict, Optional from enum import Enum import requests class WeatherCondition(Enum): CLEAR = "clear" CLOUDY = "cloudy" RAIN = "rain" HEAVY_RAIN = "heavy_rain" SNOW = "snow" FROST = "frost" HIGH_WIND = "high_wind" EXTREME_HEAT = "extreme_heat" EXTREME_COLD = "extreme_cold" @dataclass class WeatherDay: date: date condition: WeatherCondition temp_high: float temp_low: float precipitation_mm: float wind_speed_kmh: float humidity_pct: float @dataclass class ActivitySensitivity: activity_type: str min_temp: float max_temp: float max_wind: float max_precipitation: float can_work_in_rain: bool def check_work_day(weather: WeatherDay, activity: ActivitySensitivity) -> Dict: """Check if work is possible for given weather and activity""" can_work = True reasons = [] if weather.temp_low < activity.min_temp: can_work = False reasons.append(f"Temperature too low: {weather.temp_low}°C < {activity.min_temp}°C") if weather.temp_high > activity.max_temp: can_work = False reasons.append(f"Temperature too high: {weather.temp_high}°C > {activity.max_temp}°C") if weather.wind_speed_kmh > activity.max_wind: can_work = False reasons.append(f"Wind too strong: {weather.wind_speed_kmh} km/h > {activity.max_wind} km/h") if weather.precipitation_mm > activity.max_precipitation and not activity.can_work_in_rain: can_work = False reasons.append(f"Precipitation: {weather.precipitation_mm}mm") return { 'date': weather.date, 'can_work': can_work, 'reasons': reasons, 'productivity_factor': 1.0 if can_work else 0.0 } # Example concrete_work = ActivitySensitivity( activity_type="concrete_placement", min_temp=5, max_temp=35, max_wind=40, max_precipitation=2, can_work_in_rain=False ) today_weather = WeatherDay( date=date.today(), condition=WeatherCondition.RAIN, temp_high=15, temp_low=8, precipitation_mm=10, wind_speed_kmh=20, humidity_pct=80 ) result = check_work_day(today_weather, concrete_work) print(f"Can work: {result['can_work']}, Reasons: {result['reasons']}")
Comprehensive Weather Analysis System
Weather Data Integration
from dataclasses import dataclass, field from datetime import date, datetime, timedelta from typing import List, Dict, Optional, Tuple from enum import Enum import requests import json class WeatherSeverity(Enum): NORMAL = 1 CAUTION = 2 WARNING = 3 SEVERE = 4 EXTREME = 5 @dataclass class HourlyWeather: datetime: datetime temperature: float feels_like: float humidity: float wind_speed: float wind_direction: float precipitation: float precipitation_probability: float condition: WeatherCondition visibility: float uv_index: float @dataclass class DailyForecast: date: date temp_high: float temp_low: float sunrise: datetime sunset: datetime precipitation_total: float precipitation_probability: float primary_condition: WeatherCondition hourly: List[HourlyWeather] = field(default_factory=list) severity: WeatherSeverity = WeatherSeverity.NORMAL class WeatherDataService: """Weather data integration service""" def __init__(self, api_key: str = None, provider: str = "openweathermap"): self.api_key = api_key self.provider = provider self.cache: Dict[str, Dict] = {} self.cache_duration = timedelta(hours=1) def get_forecast(self, latitude: float, longitude: float, days: int = 14) -> List[DailyForecast]: """Get weather forecast for location""" cache_key = f"{latitude},{longitude}" if cache_key in self.cache: cached = self.cache[cache_key] if datetime.now() - cached['timestamp'] < self.cache_duration: return cached['data'] if self.provider == "openweathermap": forecast = self._fetch_openweathermap(latitude, longitude, days) else: forecast = self._generate_sample_forecast(days) self.cache[cache_key] = { 'timestamp': datetime.now(), 'data': forecast } return forecast def _fetch_openweathermap(self, lat: float, lon: float, days: int) -> List[DailyForecast]: """Fetch from OpenWeatherMap API""" url = f"https://api.openweathermap.org/data/2.5/forecast" params = { 'lat': lat, 'lon': lon, 'appid': self.api_key, 'units': 'metric' } try: response = requests.get(url, params=params) data = response.json() return self._parse_openweathermap(data) except Exception as e: print(f"Weather API error: {e}") return self._generate_sample_forecast(days) def _parse_openweathermap(self, data: Dict) -> List[DailyForecast]: """Parse OpenWeatherMap response""" forecasts = [] daily_data = {} for item in data.get('list', []): dt = datetime.fromtimestamp(item['dt']) day = dt.date() if day not in daily_data: daily_data[day] = { 'temps': [], 'precipitation': 0, 'conditions': [], 'hourly': [] } daily_data[day]['temps'].append(item['main']['temp']) daily_data[day]['precipitation'] += item.get('rain', {}).get('3h', 0) condition = self._map_condition(item['weather'][0]['main']) daily_data[day]['conditions'].append(condition) daily_data[day]['hourly'].append(HourlyWeather( datetime=dt, temperature=item['main']['temp'], feels_like=item['main']['feels_like'], humidity=item['main']['humidity'], wind_speed=item['wind']['speed'] * 3.6, # m/s to km/h wind_direction=item['wind'].get('deg', 0), precipitation=item.get('rain', {}).get('3h', 0), precipitation_probability=item.get('pop', 0) * 100, condition=condition, visibility=item.get('visibility', 10000) / 1000, uv_index=0 )) for day, data in daily_data.items(): primary_condition = max(set(data['conditions']), key=data['conditions'].count) forecasts.append(DailyForecast( date=day, temp_high=max(data['temps']), temp_low=min(data['temps']), sunrise=datetime.combine(day, datetime.min.time().replace(hour=6)), sunset=datetime.combine(day, datetime.min.time().replace(hour=18)), precipitation_total=data['precipitation'], precipitation_probability=max(h.precipitation_probability for h in data['hourly']), primary_condition=primary_condition, hourly=data['hourly'], severity=self._calculate_severity(primary_condition, data) )) return sorted(forecasts, key=lambda x: x.date) def _map_condition(self, condition_str: str) -> WeatherCondition: """Map API condition to enum""" mapping = { 'Clear': WeatherCondition.CLEAR, 'Clouds': WeatherCondition.CLOUDY, 'Rain': WeatherCondition.RAIN, 'Drizzle': WeatherCondition.RAIN, 'Thunderstorm': WeatherCondition.HEAVY_RAIN, 'Snow': WeatherCondition.SNOW, 'Mist': WeatherCondition.CLOUDY, 'Fog': WeatherCondition.CLOUDY } return mapping.get(condition_str, WeatherCondition.CLEAR) def _calculate_severity(self, condition: WeatherCondition, data: Dict) -> WeatherSeverity: """Calculate weather severity""" max_temp = max(data['temps']) min_temp = min(data['temps']) precip = data['precipitation'] if condition in [WeatherCondition.HEAVY_RAIN, WeatherCondition.SNOW]: if precip > 50: return WeatherSeverity.EXTREME elif precip > 25: return WeatherSeverity.SEVERE if max_temp > 40 or min_temp < -15: return WeatherSeverity.SEVERE if max_temp > 35 or min_temp < -5: return WeatherSeverity.WARNING if condition == WeatherCondition.RAIN: return WeatherSeverity.CAUTION return WeatherSeverity.NORMAL def _generate_sample_forecast(self, days: int) -> List[DailyForecast]: """Generate sample forecast for testing""" import random forecasts = [] for i in range(days): day = date.today() + timedelta(days=i) temp_base = 15 + random.uniform(-5, 10) condition = random.choice(list(WeatherCondition)) forecasts.append(DailyForecast( date=day, temp_high=temp_base + random.uniform(3, 8), temp_low=temp_base - random.uniform(3, 8), sunrise=datetime.combine(day, datetime.min.time().replace(hour=6)), sunset=datetime.combine(day, datetime.min.time().replace(hour=18)), precipitation_total=random.uniform(0, 20) if condition == WeatherCondition.RAIN else 0, precipitation_probability=random.uniform(0, 100) if condition == WeatherCondition.RAIN else 10, primary_condition=condition, severity=WeatherSeverity.NORMAL )) return forecasts
Activity Weather Sensitivity
@dataclass class WeatherThresholds: min_temp: float = -10 max_temp: float = 45 max_wind: float = 50 max_precipitation: float = 50 max_snow_depth: float = 20 min_visibility: float = 0.5 # km @dataclass class ConstructionActivity: activity_id: str activity_name: str category: str thresholds: WeatherThresholds indoor: bool = False rain_sensitive: bool = True frost_sensitive: bool = False productivity_factors: Dict[WeatherCondition, float] = field(default_factory=dict) def __post_init__(self): if not self.productivity_factors: self.productivity_factors = { WeatherCondition.CLEAR: 1.0, WeatherCondition.CLOUDY: 0.95, WeatherCondition.RAIN: 0.3 if self.rain_sensitive else 0.8, WeatherCondition.HEAVY_RAIN: 0.0 if self.rain_sensitive else 0.5, WeatherCondition.SNOW: 0.2, WeatherCondition.FROST: 0.5 if self.frost_sensitive else 0.8, WeatherCondition.HIGH_WIND: 0.3, WeatherCondition.EXTREME_HEAT: 0.6, WeatherCondition.EXTREME_COLD: 0.4 } class ActivityWeatherAnalyzer: """Analyze weather impact on construction activities""" # Default activity definitions ACTIVITY_TEMPLATES = { 'concrete_placement': ConstructionActivity( activity_id='ACT-001', activity_name='Concrete Placement', category='structural', thresholds=WeatherThresholds(min_temp=5, max_temp=35, max_precipitation=2, max_wind=40), rain_sensitive=True, frost_sensitive=True ), 'steel_erection': ConstructionActivity( activity_id='ACT-002', activity_name='Steel Erection', category='structural', thresholds=WeatherThresholds(max_wind=35, max_precipitation=10), rain_sensitive=False ), 'roofing': ConstructionActivity( activity_id='ACT-003', activity_name='Roofing', category='envelope', thresholds=WeatherThresholds(min_temp=0, max_precipitation=0, max_wind=30), rain_sensitive=True ), 'excavation': ConstructionActivity( activity_id='ACT-004', activity_name='Excavation', category='earthwork', thresholds=WeatherThresholds(min_temp=-5, max_precipitation=25), rain_sensitive=True, frost_sensitive=True ), 'painting_exterior': ConstructionActivity( activity_id='ACT-005', activity_name='Exterior Painting', category='finishing', thresholds=WeatherThresholds(min_temp=10, max_temp=35, max_precipitation=0, max_wind=25), rain_sensitive=True ), 'masonry': ConstructionActivity( activity_id='ACT-006', activity_name='Masonry Work', category='structural', thresholds=WeatherThresholds(min_temp=5, max_temp=32, max_precipitation=5), rain_sensitive=True, frost_sensitive=True ), 'crane_operations': ConstructionActivity( activity_id='ACT-007', activity_name='Crane Operations', category='equipment', thresholds=WeatherThresholds(max_wind=30, min_visibility=1.0), rain_sensitive=False ), 'electrical_exterior': ConstructionActivity( activity_id='ACT-008', activity_name='Exterior Electrical', category='MEP', thresholds=WeatherThresholds(max_precipitation=0), rain_sensitive=True ), 'interior_work': ConstructionActivity( activity_id='ACT-009', activity_name='Interior Work', category='finishing', thresholds=WeatherThresholds(), indoor=True, rain_sensitive=False ) } def __init__(self): self.activities = dict(self.ACTIVITY_TEMPLATES) def add_activity(self, activity: ConstructionActivity): """Add custom activity""" self.activities[activity.activity_id] = activity def analyze_day(self, weather: DailyForecast, activities: List[str]) -> Dict[str, Dict]: """Analyze weather impact for specific day and activities""" results = {} for activity_id in activities: activity = self.activities.get(activity_id) if not activity: continue impact = self._calculate_impact(weather, activity) results[activity_id] = impact return results def _calculate_impact(self, weather: DailyForecast, activity: ConstructionActivity) -> Dict: """Calculate weather impact on activity""" if activity.indoor: return { 'can_work': True, 'productivity': 1.0, 'issues': [], 'recommendations': [] } issues = [] productivity = 1.0 # Temperature check if weather.temp_low < activity.thresholds.min_temp: issues.append(f"Low temperature: {weather.temp_low}°C") if activity.frost_sensitive: productivity *= 0.0 else: productivity *= 0.5 if weather.temp_high > activity.thresholds.max_temp: issues.append(f"High temperature: {weather.temp_high}°C") productivity *= 0.6 # Precipitation check if weather.precipitation_total > activity.thresholds.max_precipitation: issues.append(f"Precipitation: {weather.precipitation_total}mm") if activity.rain_sensitive: productivity *= 0.0 else: productivity *= 0.7 # Condition-based productivity condition_factor = activity.productivity_factors.get( weather.primary_condition, 1.0 ) productivity *= condition_factor # Generate recommendations recommendations = [] if productivity < 0.5 and productivity > 0: recommendations.append("Consider rescheduling to more favorable day") if weather.temp_low < activity.thresholds.min_temp + 5: recommendations.append("Plan for cold weather precautions") if weather.precipitation_probability > 50: recommendations.append("Have rain contingency plan ready") can_work = productivity > 0 return { 'activity_name': activity.activity_name, 'can_work': can_work, 'productivity': round(productivity, 2), 'issues': issues, 'recommendations': recommendations, 'weather_condition': weather.primary_condition.value, 'temperature_range': f"{weather.temp_low}°C - {weather.temp_high}°C" } def find_optimal_days(self, forecast: List[DailyForecast], activity_id: str, min_productivity: float = 0.8) -> List[date]: """Find optimal days for an activity""" activity = self.activities.get(activity_id) if not activity: return [] optimal = [] for day in forecast: impact = self._calculate_impact(day, activity) if impact['productivity'] >= min_productivity: optimal.append(day.date) return optimal
Schedule Weather Integration
from datetime import date, timedelta from typing import List, Dict import pandas as pd @dataclass class ScheduledActivity: activity_id: str activity_name: str activity_type: str # Maps to ACTIVITY_TEMPLATES planned_start: date planned_end: date duration_days: int is_critical: bool = False class ScheduleWeatherOptimizer: """Optimize construction schedule based on weather""" def __init__(self, weather_service: WeatherDataService, activity_analyzer: ActivityWeatherAnalyzer): self.weather = weather_service self.analyzer = activity_analyzer def analyze_schedule(self, schedule: List[ScheduledActivity], location: Tuple[float, float]) -> Dict: """Analyze schedule against weather forecast""" forecast = self.weather.get_forecast(location[0], location[1]) forecast_dict = {f.date: f for f in forecast} analysis = { 'activities': [], 'weather_delays': 0, 'risk_days': [], 'recommendations': [] } for activity in schedule: activity_analysis = self._analyze_activity( activity, forecast_dict ) analysis['activities'].append(activity_analysis) if activity_analysis['expected_delay'] > 0: analysis['weather_delays'] += activity_analysis['expected_delay'] analysis['risk_days'].extend(activity_analysis['risk_days']) # Generate overall recommendations if analysis['weather_delays'] > 5: analysis['recommendations'].append( f"Schedule shows {analysis['weather_delays']} potential weather delay days. " "Consider buffer time or alternative scheduling." ) return analysis def _analyze_activity(self, activity: ScheduledActivity, forecast: Dict[date, DailyForecast]) -> Dict: """Analyze single activity against weather""" result = { 'activity_id': activity.activity_id, 'activity_name': activity.activity_name, 'planned_start': activity.planned_start, 'planned_end': activity.planned_end, 'day_analysis': [], 'risk_days': [], 'expected_delay': 0, 'avg_productivity': 1.0 } current_date = activity.planned_start productivities = [] delay_days = 0 while current_date <= activity.planned_end: if current_date in forecast: weather = forecast[current_date] impact = self.analyzer._calculate_impact( weather, self.analyzer.activities.get(activity.activity_type, self.analyzer.ACTIVITY_TEMPLATES.get('interior_work')) ) productivities.append(impact['productivity']) day_info = { 'date': current_date, 'can_work': impact['can_work'], 'productivity': impact['productivity'], 'weather': weather.primary_condition.value } result['day_analysis'].append(day_info) if not impact['can_work']: delay_days += 1 result['risk_days'].append({ 'date': current_date, 'activity': activity.activity_name, 'reason': weather.primary_condition.value }) elif impact['productivity'] < 0.7: delay_days += (1 - impact['productivity']) current_date += timedelta(days=1) result['expected_delay'] = round(delay_days) result['avg_productivity'] = sum(productivities) / len(productivities) if productivities else 1.0 return result def suggest_reschedule(self, activity: ScheduledActivity, location: Tuple[float, float], flexibility_days: int = 7) -> Optional[date]: """Suggest better start date for activity""" forecast = self.weather.get_forecast(location[0], location[1]) best_start = None best_avg_productivity = 0 for offset in range(-flexibility_days, flexibility_days + 1): test_start = activity.planned_start + timedelta(days=offset) test_end = test_start + timedelta(days=activity.duration_days - 1) productivities = [] for f in forecast: if test_start <= f.date <= test_end: act_template = self.analyzer.activities.get(activity.activity_type) if act_template: impact = self.analyzer._calculate_impact(f, act_template) productivities.append(impact['productivity']) if productivities: avg = sum(productivities) / len(productivities) if avg > best_avg_productivity: best_avg_productivity = avg best_start = test_start if best_start and best_start != activity.planned_start: return best_start return None def generate_weather_report(self, schedule: List[ScheduledActivity], location: Tuple[float, float], output_path: str) -> str: """Generate weather impact report""" analysis = self.analyze_schedule(schedule, location) with pd.ExcelWriter(output_path, engine='openpyxl') as writer: # Summary summary = pd.DataFrame([{ 'Total Activities': len(schedule), 'Weather Delay Days': analysis['weather_delays'], 'High Risk Days': len(analysis['risk_days']), 'Recommendations': len(analysis['recommendations']) }]) summary.to_excel(writer, sheet_name='Summary', index=False) # Activity details activity_data = [] for act in analysis['activities']: activity_data.append({ 'Activity': act['activity_name'], 'Start': act['planned_start'], 'End': act['planned_end'], 'Avg Productivity': f"{act['avg_productivity']:.0%}", 'Expected Delay': f"{act['expected_delay']} days" }) pd.DataFrame(activity_data).to_excel(writer, sheet_name='Activities', index=False) # Risk days if analysis['risk_days']: pd.DataFrame(analysis['risk_days']).to_excel( writer, sheet_name='Risk_Days', index=False ) return output_path
Quick Reference
| Activity Type | Min Temp | Max Precip | Max Wind | Rain Sensitive |
|---|---|---|---|---|
| Concrete | 5°C | 2mm | 40 km/h | Yes |
| Steel Erection | -10°C | 10mm | 35 km/h | No |
| Roofing | 0°C | 0mm | 30 km/h | Yes |
| Excavation | -5°C | 25mm | 50 km/h | Partial |
| Exterior Painting | 10°C | 0mm | 25 km/h | Yes |
| Masonry | 5°C | 5mm | 40 km/h | Yes |
| Crane Operations | -15°C | 20mm | 30 km/h | No |
Resources
- OpenWeatherMap API: https://openweathermap.org/api
- Weather Underground: https://www.wunderground.com/weather/api
- DDC Website: https://datadrivenconstruction.io
Next Steps
- See
for schedule visualization4d-simulation - See
for weather risk predictionrisk-assessment-ml - See
for delivery schedulingsite-logistics-optimization