关于 open-data-integrator
用于整合政府、行业、天气和地理空间等来源的建筑开放数据,面向建筑数据工作流,并提供材料价格、劳动力费率和天气数据连接器。
主要功能
- 01整合建筑数据集
- 02天气数据连接器
- 03材料价格指数
- 04劳动力费率数据
- 05天气风险评估
适用场景
- 01获取材料价格指数
- 02丰富建筑项目数据
- 03评估项目日期的天气风险
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ datadrivenconstruction/ ddc_skills_for_ai_agents_in_construction --skill open-data-integrator安装后在 Claude Code 中输入/open-data-integrator确认已加载。
附带文件claw.jsoninstructions.md
详细介绍
开放数据集成器
概述
根据 DDC 方法论(第 2.2 章),此技能可集成来自各种来源的开放建筑数据集,例如政府数据库、行业基准、天气服务和地理空间数据。
图书参考:《Доминирование открытых данных》/《Open Data Dominance》
快速开始
from dataclasses import dataclass, field
from enum import Enum
from typing import List, Dict, Optional, Any, Callable
from datetime import datetime, date
import json
import requests
from abc import ABC, abstractmethod
class DataSourceType(Enum):
"""Types of open data sources"""
GOVERNMENT = "government" # Government statistics
INDUSTRY_BENCHMARK = "benchmark" # Industry benchmarks
WEATHER = "weather" # Weather data
GEOSPATIAL = "geospatial" # Geographic data
MATERIAL_PRICES = "material_prices" # Material cost indices
LABOR_RATES = "labor_rates" # Labor cost data
BUILDING_PERMITS = "permits" # Permit data
ENERGY = "energy" # Energy prices/data
ECONOMIC = "economic" # Economic indicators
class UpdateFrequency(Enum):
"""Data update frequency"""
REALTIME = "realtime"
HOURLY = "hourly"
DAILY = "daily"
WEEKLY = "weekly"
MONTHLY = "monthly"
QUARTERLY = "quarterly"
ANNUAL = "annual"
@dataclass
class OpenDataSource:
"""Definition of an open data source"""
id: str
name: str
source_type: DataSourceType
url: str
api_key_required: bool = False
update_frequency: UpdateFrequency = UpdateFrequency.DAILY
format: str = "json"
license: str = "open"
description: Optional[str] = None
fields: List[str] = field(default_factory=list)
@dataclass
class DataRecord:
"""A single data record from a source"""
source_id: str
timestamp: datetime
data: Dict[str, Any]
metadata: Dict[str, Any] = field(default_factory=dict)
@dataclass
class IntegrationResult:
"""Result of data integration"""
source: str
records_fetched: int
records_processed: int
errors: List[str]
last_updated: datetime
sample_data: List[Dict]
@dataclass
class EnrichedData:
"""Data enriched with open data"""
original_data: Dict[str, Any]
enrichments: Dict[str, Any]
sources_used: List[str]
confidence: float
class OpenDataConnector(ABC):
"""Base class for open data connectors"""
@abstractmethod
def fetch(self, params: Dict) -> List[DataRecord]:
pass
@abstractmethod
def get_metadata(self) -> Dict:
pass
class WeatherDataConnector(OpenDataConnector):
"""Connector for weather data (e.g., OpenWeatherMap)"""
def __init__(self, api_key: Optional[str] = None):
self.api_key = api_key
self.base_url = "https://api.openweathermap.org/data/2.5"
def fetch(
self,
params: Dict
) -> List[DataRecord]:
"""Fetch weather data for location"""
lat = params.get("lat")
lon = params.get("lon")
start_date = params.get("start_date")
end_date = params.get("end_date")
# Simulate API call (in production, use actual API)
records = []
# Generate sample historical data
current = start_date
while current <= end_date:
records.append(DataRecord(
source_id="openweathermap",
timestamp=datetime.combine(current, datetime.min.time()),
data={
"date": current.isoformat(),
"temp_max": 25.0,
"temp_min": 15.0,
"precipitation": 0.0,
"wind_speed": 10.0,
"weather_code": "clear"
},
metadata={"lat": lat, "lon": lon}
))
current = date(current.year, current.month, current.day + 1) if current.day < 28 else date(current.year, current.month + 1 if current.month < 12 else 1, 1)
return records[:30] # Limit for demo
def get_metadata(self) -> Dict:
return {
"source": "OpenWeatherMap",
"type": DataSourceType.WEATHER.value,
"frequency": UpdateFrequency.HOURLY.value,
"fields": ["temp_max", "temp_min", "precipitation", "wind_speed"]
}
class MaterialPriceConnector(OpenDataConnector):
"""Connector for material price indices"""
def __init__(self, region: str = "US"):
self.region = region
self.price_indices = self._load_indices()
def _load_indices(self) -> Dict[str, Dict]:
"""Load material price indices"""
return {
"concrete": {"base": 100, "current": 125, "trend": "up"},
"steel": {"base": 100, "current": 145, "trend": "up"},
"lumber": {"base": 100, "current": 180, "trend": "stable"},
"copper": {"base": 100, "current": 135, "trend": "up"},
"asphalt": {"base": 100, "current": 115, "trend": "stable"},
"gypsum": {"base": 100, "current": 110, "trend": "stable"},
"glass": {"base": 100, "current": 105, "trend": "down"},
"cement": {"base": 100, "current": 120, "trend": "up"},
}
def fetch(self, params: Dict) -> List[DataRecord]:
"""Fetch material price data"""
materials = params.get("materials", list(self.price_indices.keys()))
records = []
for material in materials:
if material in self.price_indices:
records.append(DataRecord(
source_id="material_prices",
timestamp=datetime.now(),
data={
"material": material,
"region": self.region,
**self.price_indices[material]
}
))
return records
def get_metadata(self) -> Dict:
return {
"source": "Material Price Index",
"type": DataSourceType.MATERIAL_PRICES.value,
"frequency": UpdateFrequency.MONTHLY.value,
"materials": list(self.price_indices.keys())
}
class LaborRateConnector(OpenDataConnector):
"""Connector for labor rate data"""
def __init__(self, region: str = "US"):
self.region = region
self.labor_rates = self._load_rates()
def _load_rates(self) -> Dict[str, Dict]:
"""Load labor rates by trade"""
return {
"carpenter": {"hourly": 45.00, "burden_rate": 1.35},
"electrician": {"hourly": 55.00, "burden_rate": 1.40},
"plumber": {"hourly": 52.00, "burden_rate": 1.38},
"ironworker": {"hourly": 58.00, "burden_rate": 1.42},
"laborer": {"hourly": 32.00, "burden_rate": 1.30},
"operator": {"hourly": 48.00, "burden_rate": 1.35},
"mason": {"hourly": 50.00, "burden_rate": 1.36},
"painter": {"hourly": 38.00, "burden_rate": 1.32},
"hvac_tech": {"hourly": 54.00, "burden_rate": 1.38},
"welder": {"hourly": 52.00, "burden_rate": 1.40},
}
def fetch(self, params: Dict) -> List[DataRecord]:
"""Fetch labor rate data"""
trades = params.get("trades", list(self.labor_rates.keys()))
records = []
for trade in trades:
if trade in self.labor_rates:
rate_data = self.labor_rates[trade]
records.append(DataRecord(
source_id="labor_rates",
timestamp=datetime.now(),
data={
"trade": trade,
"region": self.region,
"hourly_rate": rate_data["hourly"],
"burden_rate": rate_data["burden_rate"],
"fully_loaded": rate_data["hourly"] * rate_data["burden_rate"]
}
))
return records
def get_metadata(self) -> Dict:
return {
"source": "Labor Rate Database",
"type": DataSourceType.LABOR_RATES.value,
"frequency": UpdateFrequency.QUARTERLY.value,
"trades": list(self.labor_rates.keys())
}
class BuildingPermitConnector(OpenDataConnector):
"""Connector for building permit data"""
def __init__(self, jurisdiction: str = "default"):
self.jurisdiction = jurisdiction
def fetch(self, params: Dict) -> List[DataRecord]:
"""Fetch permit data"""
# Simulate permit data
permit_types = ["new_construction", "renovation", "addition", "demolition"]
records = []
for ptype in permit_types:
records.append(DataRecord(
source_id="building_permits",
timestamp=datetime.now(),
data={
"permit_type": ptype,
"jurisdiction": self.jurisdiction,
"count_ytd": 150,
"total_value": 25000000,
"avg_processing_days": 21
}
))
return records
def get_metadata(self) -> Dict:
return {
"source": "Building Permit Database",
"type": DataSourceType.BUILDING_PERMITS.value,
"frequency": UpdateFrequency.DAILY.value
}
class OpenDataIntegrator:
"""
Integrate open construction datasets.
Based on DDC methodology Chapter 2.2.
"""
def __init__(self, region: str = "US"):
self.region = region
self.connectors: Dict[str, OpenDataConnector] = {}
self.cache: Dict[str, List[DataRecord]] = {}
self.cache_expiry: Dict[str, datetime] = {}
self._register_default_connectors()
def _register_default_connectors(self):
"""Register default data connectors"""
self.register_connector("weather", WeatherDataConnector())
self.register_connector("material_prices", MaterialPriceConnector(self.region))
self.register_connector("labor_rates", LaborRateConnector(self.region))
self.register_connector("permits", BuildingPermitConnector())
def register_connector(
self,
name: str,
connector: OpenDataConnector
):
"""Register a data connector"""
self.connectors[name] = connector
def fetch_data(
self,
source: str,
params: Optional[Dict] = None,
use_cache: bool = True
) -> IntegrationResult:
"""
Fetch data from a source.
Args:
source: Name of the data source
params: Query parameters
use_cache: Whether to use cached data
Returns:
Integration result with fetched data
"""
if source not in self.connectors:
return IntegrationResult(
source=source,
records_fetched=0,
records_processed=0,
errors=[f"Unknown source: {source}"],
last_updated=datetime.now(),
sample_data=[]
)
# Check cache
cache_key = f"{source}_{json.dumps(params or {}, sort_keys=True)}"
if use_cache and cache_key in self.cache:
expiry = self.cache_expiry.get(cache_key)
if expiry and expiry > datetime.now():
cached = self.cache[cache_key]
return IntegrationResult(
source=source,
records_fetched=len(cached),
records_processed=len(cached),
errors=[],
last_updated=expiry,
sample_data=[r.data for r in cached[:5]]
)
# Fetch fresh data
connector = self.connectors[source]
errors = []
try:
records = connector.fetch(params or {})
# Cache the results
self.cache[cache_key] = records
self.cache_expiry[cache_key] = datetime.now()
return IntegrationResult(
source=source,
records_fetched=len(records),
records_processed=len(records),
errors=errors,
last_updated=datetime.now(),
sample_data=[r.data for r in records[:5]]
)
except Exception as e:
errors.append(str(e))
return IntegrationResult(
source=source,
records_fetched=0,
records_processed=0,
errors=errors,
last_updated=datetime.now(),
sample_data=[]
)
def enrich_project_data(
self,
project_data: Dict[str, Any],
enrichment_sources: Optional[List[str]] = None
) -> EnrichedData:
"""
Enrich project data with open data.
Args:
project_data: Original project data
enrichment_sources: Sources to use for enrichment
Returns:
Enriched data
"""
sources = enrichment_sources or ["material_prices", "labor_rates", "weather"]
enrichments = {}
sources_used = []
# Material price enrichment
if "material_prices" in sources and "materials" in project_data:
materials = project_data["materials"]
result = self.fetch_data("material_prices", {"materials": materials})
if result.records_fetched > 0:
enrichments["material_price_indices"] = result.sample_data
sources_used.append("material_prices")
# Labor rate enrichment
if "labor_rates" in sources and "trades" in project_data:
trades = project_data["trades"]
result = self.fetch_data("labor_rates", {"trades": trades})
if result.records_fetched > 0:
enrichments["labor_rates"] = result.sample_data
sources_used.append("labor_rates")
# Weather enrichment
if "weather" in sources and "location" in project_data:
loc = project_data["location"]
params = {
"lat": loc.get("lat"),
"lon": loc.get("lon"),
"start_date": project_data.get("start_date", date.today()),
"end_date": project_data.get("end_date", date.today())
}
result = self.fetch_data("weather", params)
if result.records_fetched > 0:
enrichments["weather_forecast"] = result.sample_data
sources_used.append("weather")
# Calculate confidence based on enrichment success
confidence = len(sources_used) / len(sources) if sources else 0
return EnrichedData(
original_data=project_data,
enrichments=enrichments,
sources_used=sources_used,
confidence=confidence
)
def get_cost_indices(
self,
materials: Optional[List[str]] = None,
trades: Optional[List[str]] = None
) -> Dict:
"""Get current cost indices"""
indices = {
"timestamp": datetime.now().isoformat(),
"region": self.region
}
if materials:
result = self.fetch_data("material_prices", {"materials": materials})
indices["materials"] = result.sample_data
if trades:
result = self.fetch_data("labor_rates", {"trades": trades})
indices["labor"] = result.sample_data
return indices
def get_weather_risk(
self,
lat: float,
lon: float,
start_date: date,
end_date: date
) -> Dict:
"""Assess weather risk for project period"""
result = self.fetch_data("weather", {
"lat": lat,
"lon": lon,
"start_date": start_date,
"end_date": end_date
})
if result.records_fetched == 0:
return {"error": "No weather data available"}
# Calculate risk metrics
rain_days = sum(1 for d in result.sample_data
if d.get("precipitation", 0) > 5)
extreme_temp_days = sum(1 for d in result.sample_data
if d.get("temp_max", 0) > 35 or d.get("temp_min", 0) < 0)
total_days = len(result.sample_data)
risk_score = (rain_days + extreme_temp_days) / total_days if total_days > 0 else 0
return {
"total_days": total_days,
"rain_days": rain_days,
"extreme_temperature_days": extreme_temp_days,
"risk_score": risk_score,
"risk_level": "high" if risk_score > 0.3 else "medium" if risk_score > 0.1 else "low"
}
def list_sources(self) -> List[Dict]:
"""List all available data sources"""
sources = []
for name, connector in self.connectors.items():
meta = connector.get_metadata()
sources.append({
"name": name,
**meta
})
return sources
def generate_report(self) -> str:
"""Generate data availability report"""
output = """
# Open Data Integration Report
## Available Sources
"""
for source in self.list_sources():
output += f"""
### {source['name'].title()}
- **Type:** {source['type']}
- **Update Frequency:** {source['frequency']}
"""
output += """
## Cache Status
"""
for key, expiry in self.cache_expiry.items():
status = "valid" if expiry > datetime.now() else "expired"
output += f"- {key}: {status}\n"
return output
常见用例
获取材料价格
integrator = OpenDataIntegrator(region="US")
# Get material price indices
result = integrator.fetch_data("material_prices", {
"materials": ["concrete", "steel", "lumber"]
})
print(f"Fetched: {result.records_fetched} records")
for record in result.sample_data:
print(f" {record['material']}: index={record['current']}, trend={record['trend']}")
丰富项目数据
project = {
"name": "Office Building",
"materials": ["concrete", "steel", "glass"],
"trades": ["carpenter", "electrician", "plumber"],
"location": {"lat": 40.7128, "lon": -74.0060},
"start_date": date(2024, 6, 1),
"end_date": date(2024, 12, 31)
}
enriched = integrator.enrich_project_data(project)
print(f"Sources used: {enriched.sources_used}")
print(f"Confidence: {enriched.confidence:.0%}")
print(f"Material indices: {enriched.enrichments.get('material_price_indices')}")
评估天气风险
risk = integrator.get_weather_risk(
lat=40.7128,
lon=-74.0060,
start_date=date(2024, 6, 1),
end_date=date(2024, 8, 31)
)
print(f"Risk Level: {risk['risk_level']}")
print(f"Rain Days: {risk['rain_days']}")
快速参考
| 组件 | 用途 |
|---|---|
OpenDataIntegrator | 主要集成引擎 |
OpenDataConnector | 基础连接器类 |
WeatherDataConnector | 天气 API 连接器 |
MaterialPriceConnector | 材料价格指数 |
LaborRateConnector | 劳动力费率数据 |
EnrichedData | 丰富后的数据结果 |
资源
- 图书:Artem Boiko 的《Data-Driven Construction》,第 2.2 章
- 网站:https://datadrivenconstruction.io
后续步骤
- 使用 ontology-mapper 进行语义映射
- 使用 cost-prediction 和指数
- 使用 weather-impact-analysis 进行排期
评论
支持 Markdown