
sensor-data-aggregator
Aggregate and analyze IoT sensor data from construction sites. Collect data from multiple sensor types, detect anomalies, and trigger alerts for safety and quality monitoring.
- Installs
- 118
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/datadrivenconstruction/ddc_skills_for_ai_agents_in_construction --skill sensor-data-aggregator- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads sensor-data-aggregator automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
Sensor Data Aggregator
Overview
Collect, aggregate, and analyze data from IoT sensors deployed across construction sites. Support real-time monitoring of environmental conditions, equipment status, structural integrity, and worker safety through unified data processing.
IoT Sensor Architecture
┌─────────────────────────────────────────────────────────────────┐
│ SENSOR DATA AGGREGATION │
├─────────────────────────────────────────────────────────────────┤
│ │
│ SENSORS AGGREGATOR OUTPUTS │
│ ─────── ────────── ─────── │
│ │
│ 🌡️ Temperature ─────┐ 📊 Dashboard │
│ 💧 Humidity ─────┤ ┌──────────────┐ ⚠️ Alerts │
│ 📊 Vibration ─────┼───→│ AGGREGATE │───→ 📈 Analytics │
│ 🔊 Noise ─────┤ │ PROCESS │ 📋 Reports │
│ 💨 Air Quality ─────┤ │ ANALYZE │ 🔄 API │
│ 📍 Location ─────┘ └──────────────┘ │
│ │
│ DATA FLOW: │
│ Raw → Validate → Transform → Store → Analyze → Alert │
│ │
│ ANALYSIS: │
│ • Real-time monitoring │
│ • Trend detection │
│ • Anomaly identification │
│ • Threshold alerting │
│ │
└─────────────────────────────────────────────────────────────────┘
Technical Implementation
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Callable, Tuple
from datetime import datetime, timedelta
from enum import Enum
import statistics
import json
class SensorType(Enum):
TEMPERATURE = "temperature"
HUMIDITY = "humidity"
VIBRATION = "vibration"
NOISE = "noise"
AIR_QUALITY = "air_quality"
DUST = "dust"
GAS = "gas"
PRESSURE = "pressure"
STRAIN = "strain"
TILT = "tilt"
GPS = "gps"
PROXIMITY = "proximity"
class AlertSeverity(Enum):
INFO = "info"
WARNING = "warning"
CRITICAL = "critical"
EMERGENCY = "emergency"
class DataQuality(Enum):
GOOD = "good"
SUSPECT = "suspect"
BAD = "bad"
MISSING = "missing"
@dataclass
class SensorReading:
sensor_id: str
sensor_type: SensorType
timestamp: datetime
value: float
unit: str
quality: DataQuality = DataQuality.GOOD
location: Optional[Dict] = None
metadata: Dict = field(default_factory=dict)
@dataclass
class Sensor:
id: str
name: str
sensor_type: SensorType
unit: str
location: Dict # {zone, floor, coordinates}
thresholds: Dict # {warning, critical, min, max}
calibration_date: datetime
battery_level: float = 100.0
status: str = "active"
@dataclass
class Alert:
id: str
sensor_id: str
sensor_type: SensorType
severity: AlertSeverity
timestamp: datetime
value: float
threshold: float
message: str
acknowledged: bool = False
resolved: bool = False
@dataclass
class AggregatedMetric:
sensor_type: SensorType
period_start: datetime
period_end: datetime
readings_count: int
min_value: float
max_value: float
avg_value: float
std_dev: float
alerts_triggered: int
class SensorDataAggregator:
"""Aggregate and analyze IoT sensor data."""
# Default thresholds by sensor type
DEFAULT_THRESHOLDS = {
SensorType.TEMPERATURE: {"warning": 35, "critical": 40, "unit": "°C"},
SensorType.HUMIDITY: {"warning": 80, "critical": 90, "unit": "%"},
SensorType.VIBRATION: {"warning": 10, "critical": 25, "unit": "mm/s"},
SensorType.NOISE: {"warning": 85, "critical": 100, "unit": "dB"},
SensorType.AIR_QUALITY: {"warning": 100, "critical": 150, "unit": "AQI"},
SensorType.DUST: {"warning": 3, "critical": 10, "unit": "mg/m³"},
SensorType.GAS: {"warning": 20, "critical": 50, "unit": "ppm"},
}
def __init__(self, site_name: str):
self.site_name = site_name
self.sensors: Dict[str, Sensor] = {}
self.readings: List[SensorReading] = []
self.alerts: List[Alert] = []
self.alert_handlers: List[Callable] = []
def register_sensor(self, id: str, name: str, sensor_type: SensorType,
unit: str, location: Dict,
thresholds: Dict = None) -> Sensor:
"""Register a new sensor."""
if thresholds is None:
thresholds = self.DEFAULT_THRESHOLDS.get(sensor_type, {})
sensor = Sensor(
id=id,
name=name,
sensor_type=sensor_type,
unit=unit,
location=location,
thresholds=thresholds,
calibration_date=datetime.now()
)
self.sensors[id] = sensor
return sensor
def ingest_reading(self, sensor_id: str, value: float,
timestamp: datetime = None,
metadata: Dict = None) -> SensorReading:
"""Ingest a sensor reading."""
if sensor_id not in self.sensors:
raise ValueError(f"Unknown sensor: {sensor_id}")
sensor = self.sensors[sensor_id]
# Validate data quality
quality = self._validate_reading(sensor, value)
reading = SensorReading(
sensor_id=sensor_id,
sensor_type=sensor.sensor_type,
timestamp=timestamp or datetime.now(),
value=value,
unit=sensor.unit,
quality=quality,
location=sensor.location,
metadata=metadata or {}
)
self.readings.append(reading)
# Check thresholds
if quality == DataQuality.GOOD:
self._check_thresholds(sensor, reading)
return reading
def ingest_batch(self, readings: List[Dict]) -> int:
"""Ingest multiple readings at once."""
count = 0
for r in readings:
try:
self.ingest_reading(
sensor_id=r['sensor_id'],
value=r['value'],
timestamp=r.get('timestamp', datetime.now()),
metadata=r.get('metadata')
)
count += 1
except Exception:
pass # Log error but continue
return count
def _validate_reading(self, sensor: Sensor, value: float) -> DataQuality:
"""Validate reading quality."""
thresholds = sensor.thresholds
# Check if value is within physical limits
if 'min' in thresholds and value < thresholds['min']:
return DataQuality.SUSPECT
if 'max' in thresholds and value > thresholds['max']:
return DataQuality.SUSPECT
# Check for sudden spikes (compare with recent readings)
recent = self.get_recent_readings(sensor.id, minutes=5)
if len(recent) >= 3:
avg = statistics.mean([r.value for r in recent])
if abs(value - avg) > avg * 0.5: # 50% deviation
return DataQuality.SUSPECT
return DataQuality.GOOD
def _check_thresholds(self, sensor: Sensor, reading: SensorReading):
"""Check if reading exceeds thresholds."""
thresholds = sensor.thresholds
if 'critical' in thresholds and reading.value >= thresholds['critical']:
self._create_alert(sensor, reading, AlertSeverity.CRITICAL)
elif 'warning' in thresholds and reading.value >= thresholds['warning']:
self._create_alert(sensor, reading, AlertSeverity.WARNING)
def _create_alert(self, sensor: Sensor, reading: SensorReading,
severity: AlertSeverity):
"""Create and dispatch alert."""
threshold = sensor.thresholds.get(severity.value, 0)
alert = Alert(
id=f"ALERT-{len(self.alerts)+1:06d}",
sensor_id=sensor.id,
sensor_type=sensor.sensor_type,
severity=severity,
timestamp=reading.timestamp,
value=reading.value,
threshold=threshold,
message=f"{sensor.name}: {reading.value} {reading.unit} exceeds {severity.value} threshold ({threshold})"
)
self.alerts.append(alert)
# Dispatch to handlers
for handler in self.alert_handlers:
try:
handler(alert)
except Exception:
pass
def register_alert_handler(self, handler: Callable):
"""Register alert callback handler."""
self.alert_handlers.append(handler)
def get_recent_readings(self, sensor_id: str,
minutes: int = 60) -> List[SensorReading]:
"""Get recent readings for sensor."""
cutoff = datetime.now() - timedelta(minutes=minutes)
return [r for r in self.readings
if r.sensor_id == sensor_id and r.timestamp > cutoff]
def get_readings_by_type(self, sensor_type: SensorType,
start: datetime = None,
end: datetime = None) -> List[SensorReading]:
"""Get readings by sensor type."""
readings = [r for r in self.readings if r.sensor_type == sensor_type]
if start:
readings = [r for r in readings if r.timestamp >= start]
if end:
readings = [r for r in readings if r.timestamp <= end]
return readings
def aggregate_by_period(self, sensor_type: SensorType,
period_minutes: int = 60) -> List[AggregatedMetric]:
"""Aggregate readings into time periods."""
readings = self.get_readings_by_type(sensor_type)
if not readings:
return []
# Group by period
periods: Dict[datetime, List[SensorReading]] = {}
for r in readings:
# Round to period start
period_start = r.timestamp.replace(
minute=(r.timestamp.minute // period_minutes) * period_minutes,
second=0,
microsecond=0
)
if period_start not in periods:
periods[period_start] = []
periods[period_start].append(r)
# Calculate aggregates
aggregates = []
for period_start, period_readings in sorted(periods.items()):
values = [r.value for r in period_readings]
# Count alerts in period
period_end = period_start + timedelta(minutes=period_minutes)
period_alerts = len([a for a in self.alerts
if a.sensor_type == sensor_type
and period_start <= a.timestamp < period_end])
aggregates.append(AggregatedMetric(
sensor_type=sensor_type,
period_start=period_start,
period_end=period_end,
readings_count=len(values),
min_value=min(values),
max_value=max(values),
avg_value=statistics.mean(values),
std_dev=statistics.stdev(values) if len(values) > 1 else 0,
alerts_triggered=period_alerts
))
return aggregates
def detect_anomalies(self, sensor_id: str,
lookback_hours: int = 24) -> List[Dict]:
"""Detect anomalies in sensor data."""
cutoff = datetime.now() - timedelta(hours=lookback_hours)
readings = [r for r in self.readings
if r.sensor_id == sensor_id and r.timestamp > cutoff]
if len(readings) < 10:
return []
values = [r.value for r in readings]
avg = statistics.mean(values)
std = statistics.stdev(values)
anomalies = []
for r in readings:
# Z-score based anomaly detection
if std > 0:
z_score = abs(r.value - avg) / std
if z_score > 3: # 3 standard deviations
anomalies.append({
"timestamp": r.timestamp,
"value": r.value,
"expected": avg,
"z_score": z_score,
"type": "statistical_outlier"
})
return anomalies
def get_sensor_health(self) -> List[Dict]:
"""Get health status of all sensors."""
health = []
now = datetime.now()
for sensor in self.sensors.values():
recent = self.get_recent_readings(sensor.id, minutes=30)
# Determine status
if not recent:
status = "offline"
elif sensor.battery_level < 20:
status = "low_battery"
elif any(r.quality != DataQuality.GOOD for r in recent[-5:]):
status = "degraded"
else:
status = "healthy"
health.append({
"sensor_id": sensor.id,
"sensor_name": sensor.name,
"type": sensor.sensor_type.value,
"status": status,
"battery": sensor.battery_level,
"last_reading": recent[-1].timestamp if recent else None,
"readings_30min": len(recent)
})
return sorted(health, key=lambda x: x['status'] != 'healthy', reverse=True)
def get_zone_summary(self, zone: str) -> Dict:
"""Get summary for specific zone."""
zone_sensors = [s for s in self.sensors.values()
if s.location.get('zone') == zone]
if not zone_sensors:
return {"zone": zone, "error": "No sensors in zone"}
summary = {
"zone": zone,
"sensor_count": len(zone_sensors),
"by_type": {}
}
for sensor in zone_sensors:
recent = self.get_recent_readings(sensor.id, minutes=15)
if not recent:
continue
values = [r.value for r in recent]
sensor_type = sensor.sensor_type.value
if sensor_type not in summary["by_type"]:
summary["by_type"][sensor_type] = {
"current": values[-1] if values else None,
"avg": statistics.mean(values) if values else None,
"unit": sensor.unit,
"status": "normal"
}
# Check status
thresholds = sensor.thresholds
current = values[-1]
if 'critical' in thresholds and current >= thresholds['critical']:
summary["by_type"][sensor_type]["status"] = "critical"
elif 'warning' in thresholds and current >= thresholds['warning']:
summary["by_type"][sensor_type]["status"] = "warning"
return summary
def generate_report(self) -> str:
"""Generate sensor data report."""
lines = [
"# Sensor Data Report",
"",
f"**Site:** {self.site_name}",
f"**Report Date:** {datetime.now().strftime('%Y-%m-%d %H:%M')}",
"",
"## Sensor Inventory",
"",
f"| Sensor | Type | Location | Status |",
f"|--------|------|----------|--------|"
]
health = self.get_sensor_health()
for h in health:
status_icon = "✅" if h['status'] == 'healthy' else "⚠️" if h['status'] == 'degraded' else "🔴"
lines.append(
f"| {h['sensor_name']} | {h['type']} | - | {status_icon} {h['status']} |"
)
# Recent alerts
recent_alerts = [a for a in self.alerts
if a.timestamp > datetime.now() - timedelta(hours=24)]
if recent_alerts:
lines.extend([
"",
f"## Alerts (Last 24h) - {len(recent_alerts)} total",
"",
"| Time | Sensor | Severity | Value | Threshold |",
"|------|--------|----------|-------|-----------|"
])
for alert in sorted(recent_alerts, key=lambda x: x.timestamp, reverse=True)[:20]:
sev_icon = "🔴" if alert.severity == AlertSeverity.CRITICAL else "🟡"
lines.append(
f"| {alert.timestamp.strftime('%H:%M')} | {alert.sensor_id} | "
f"{sev_icon} {alert.severity.value} | {alert.value:.1f} | {alert.threshold} |"
)
# Current readings by type
lines.extend([
"",
"## Current Readings by Type",
""
])
for sensor_type in SensorType:
readings = self.get_readings_by_type(sensor_type)
if not readings:
continue
recent = [r for r in readings
if r.timestamp > datetime.now() - timedelta(minutes=15)]
if not recent:
continue
values = [r.value for r in recent]
lines.append(
f"**{sensor_type.value}**: "
f"Avg={statistics.mean(values):.1f}, "
f"Min={min(values):.1f}, "
f"Max={max(values):.1f}"
)
return "\n".join(lines)
Quick Start
from datetime import datetime, timedelta
# Initialize aggregator
aggregator = SensorDataAggregator("Construction Site A")
# Register sensors
aggregator.register_sensor(
"TEMP-001", "Zone A Temperature",
SensorType.TEMPERATURE, "°C",
location={"zone": "A", "floor": 1, "x": 10, "y": 20},
thresholds={"warning": 32, "critical": 38, "min": -10, "max": 50}
)
aggregator.register_sensor(
"VIB-001", "Foundation Vibration",
SensorType.VIBRATION, "mm/s",
location={"zone": "Foundation", "floor": 0}
)
aggregator.register_sensor(
"DUST-001", "Dust Monitor",
SensorType.DUST, "mg/m³",
location={"zone": "A", "floor": 1}
)
# Register alert handler
def handle_alert(alert):
print(f"ALERT: {alert.severity.value} - {alert.message}")
aggregator.register_alert_handler(handle_alert)
# Ingest readings
aggregator.ingest_reading("TEMP-001", 28.5)
aggregator.ingest_reading("TEMP-001", 33.0) # Warning!
aggregator.ingest_reading("VIB-001", 5.2)
aggregator.ingest_reading("DUST-001", 2.1)
# Batch ingest
readings = [
{"sensor_id": "TEMP-001", "value": 29.0},
{"sensor_id": "VIB-001", "value": 4.8},
{"sensor_id": "DUST-001", "value": 2.5}
]
aggregator.ingest_batch(readings)
# Check sensor health
health = aggregator.get_sensor_health()
for h in health:
print(f"{h['sensor_name']}: {h['status']}")
# Get zone summary
summary = aggregator.get_zone_summary("A")
print(f"Zone A: {summary}")
# Detect anomalies
anomalies = aggregator.detect_anomalies("TEMP-001")
print(f"Anomalies found: {len(anomalies)}")
# Generate report
print(aggregator.generate_report())
Requirements
pip install (no external dependencies)
FAQ
What does the sensor-data-aggregator skill do?
Aggregate and analyze IoT sensor data from construction sites. Collect data from multiple sensor types, detect anomalies, and trigger alerts for safety and quality monitoring.
How do I install sensor-data-aggregator?
Run npx skills add https://github.com/datadrivenconstruction/ddc_skills_for_ai_agents_in_construction --skill sensor-data-aggregator in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/sensor-data-aggregator/ (personal) or .claude/skills/sensor-data-aggregator/ (project).
Which AI agents can use sensor-data-aggregator?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is sensor-data-aggregator safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains sensor-data-aggregator?
It is published by datadrivenconstruction in datadrivenconstruction/ddc_skills_for_ai_agents_in_construction. It has 118 recorded installs.
More from datadrivenconstruction/ddc_skills_for_ai_agents_in_construction
warranty-tracker
datadrivenconstruction/ddc_skills_for_ai_agents_in_construction
Track and manage construction warranties. Monitor expiration dates, claims, and manufacturer documentation.
cad-to-data
datadrivenconstruction/ddc_skills_for_ai_agents_in_construction
Convert CAD/BIM files to structured data. Extract element data from Revit, IFC, DWG, DGN files.
dwg-to-excel
datadrivenconstruction/ddc_skills_for_ai_agents_in_construction
Convert AutoCAD DWG files (1983-2026) to Excel databases using DwgExporter CLI. Extract layers, blocks, attributes, and geometry data without Autodesk licenses.
drawing-analyzer
datadrivenconstruction/ddc_skills_for_ai_agents_in_construction
Open the skill to read its SKILL.md instructions.
permit-tracking-automation
datadrivenconstruction/ddc_skills_for_ai_agents_in_construction
Automate construction permit tracking and management. Monitor application status, track renewal deadlines, manage document requirements, and integrate with municipal systems.
cost-estimation-resource
datadrivenconstruction/ddc_skills_for_ai_agents_in_construction
Calculate construction costs using resource-based method. Estimate project costs from work items, physical resource norms, and current prices.
Popular ai & agents skills
google-agents-cli-publish
google/agents-cli
This skill should be used when the user wants to "publish an agent", "publish my ADK agent", "register an agent with Gemini", "publish to Gemini" (formerly Gemini Enterprise), or needs guidance on the agents-cli publish gemini command. Also use when the user wants to "manage agents in Agent Registry", "list/update/delete registered agents", or "register an MCP server". Covers ADK vs A2A registration modes, programmatic and interactive usage, flag reference, auto-detection from deployment metadata, Agent Registry fleet management, and troubleshooting. Part of the agents-cli skills suite. Do NOT use for deployment (use google-agents-cli-deploy).
google-agents-cli-eval
google/agents-cli
This skill should be used when the user wants to "run an evaluation", "evaluate my agent", "evaluate my ADK agent", "write an eval dataset", "analyze eval failures", "compare eval results", "optimize agent", or needs guidance on the Gemini platform eval methodology and the Quality Flywheel. Covers eval metrics, dataset schema, LLM-as-judge scoring, and common failure causes. Applies to any agents-cli project, whatever framework the agent is written in. Do NOT use for agent API code patterns (ADK: use google-agents-cli-adk-code), deployment (use google-agents-cli-deploy), or project scaffolding (use google-agents-cli-scaffold).
skill-creator
anthropics/skills
Create new skills, modify and improve existing skills, and measure skill performance. Use when users want to create a skill from scratch, edit, or optimize an existing skill, run evals to test a skill, benchmark skill performance with variance analysis, or optimize a skill's description for better triggering accuracy.
codex-pet
prime-skills/runcomfy-agent-skills
Codex Pet generator on RunComfy. Build a Codex-compatible Codex Pet spritesheet.webp + pet.json from a single reference image, drop it into ${CODEXHOME:-$HOME/.codex}/pets/ / and Codex picks it up as a custom Codex Pet next to the 8 built-ins. This skill produces the exact Codex Pet atlas Codex expects (1536x1872 PNG/WebP, 8 cols x 9 rows, 192x208 cells, 9 animation states — idle, running-right, running-left, waving, jumping, failed, waiting, running, review). Calls OpenAI GPT Image 2 edit ONCE via the local RunComfy CLI as runcomfy run openai/gpt-image-2/edit to produce a canonical Codex Pet pose, then assembles all 9 animation rows programmatically with ImageMagick micro-transforms — no Codex Pro, no $imagegen, no OPENAIAPIKEY required, only RUNCOMFYTOKEN. Triggers on "codex pet", "create codex pet", "make codex pet", "hatch codex pet", "/hatch image", "desktop pet codex", "codex pets", "spritesheet.webp", or any explicit ask to build a custom pet for OpenAI Codex.
full-output-enforcement
leonxlnx/taste-skill
Overrides default LLM truncation behavior. Enforces complete code generation, bans placeholder patterns, and handles token-limit splits cleanly. Apply to any task requiring exhaustive, unabridged output.
executing-plans
obra/superpowers
Use when executing an implementation plan in the current session as the implementer yourself — your human partner chose inline execution, or no subagent tool is available