[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6FJWMM2TY2QMZY5F7CW9EQS":3,"skill-same:01M6FJWMM2TY2QMZY5F7CW9EQS":60},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":17,"publishedAt":27,"linkOnly":28,"markdownContent":29,"markdownBody":30,"frontMatter":31,"refName":33,"commitSha":34,"skillDirectory":35,"forksCount":36,"repositoryHtmlUrl":37,"repositoryHomepageUrl":6,"ownerHtmlUrl":38,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":39,"install":40,"resources":53},"01M6FJWMM2TY2QMZY5F7CW9EQS","equipment-telematics",null,"Integrate and analyze telematics data from heavy construction equipment. Track location, utilization, fuel consumption, maintenance needs, and operator behavior.","kbarbel640-del\u002Fskills","kbarbel640-del","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F255315490?v=4","01M6CJPPQGCCESXMG3K7BBGQ5J","01M6CJPPX2PB2BCZERFVM2C2B3",1,"MIT","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdatadrivenconstruction\u002Fequipment-telematics\u002FSKILL.md",[],[18,19,20,21,22,23,24,25,26],"equipment telematics","operator","data","analyze","behavior","utilization","integrate","maintenance","location","2026-11-10 10:12:30",false,"---\r\nslug: \"equipment-telematics\"\r\ndisplay_name: \"Equipment Telematics\"\r\ndescription: \"Integrate and analyze telematics data from heavy construction equipment. Track location, utilization, fuel consumption, maintenance needs, and operator behavior.\"\r\n---\r\n\r\n# Equipment Telematics\r\n\r\n## Overview\r\n\r\nIntegrate telematics data from heavy construction equipment (excavators, cranes, loaders, trucks) to monitor utilization, track location, analyze fuel efficiency, predict maintenance needs, and ensure safe operation.\r\n\r\n## Telematics Data Flow\r\n\r\n```\r\n┌─────────────────────────────────────────────────────────────────┐\r\n│                  EQUIPMENT TELEMATICS                            │\r\n├─────────────────────────────────────────────────────────────────┤\r\n│                                                                  │\r\n│  EQUIPMENT                  TELEMATICS              ANALYTICS   │\r\n│  ─────────                  ──────────              ─────────   │\r\n│                                                                  │\r\n│  ? Excavator  ────┐       ? Location              ? Utilization│\r\n│  ?️ Crane      ────┼──────→ ? Engine Hours ────────→ ⛽ Fuel      │\r\n│  ? Truck      ────┤       ⛽ Fuel Level             ? Maintenance│\r\n│  ? Loader     ────┘       ⚡ Performance            ? Operator   │\r\n│                                                                  │\r\n│  METRICS TRACKED:                                               │\r\n│  • GPS location and geofencing                                  │\r\n│  • Engine hours and idle time                                   │\r\n│  • Fuel consumption rate                                        │\r\n│  • Load cycles and productivity                                 │\r\n│  • Fault codes and diagnostics                                  │\r\n│  • Operator behavior and safety                                 │\r\n│                                                                  │\r\n└─────────────────────────────────────────────────────────────────┘\r\n```\r\n\r\n## Technical Implementation\r\n\r\n```python\r\nfrom dataclasses import dataclass, field\r\nfrom typing import List, Dict, Optional, Tuple\r\nfrom datetime import datetime, timedelta\r\nfrom enum import Enum\r\nimport statistics\r\nimport math\r\n\r\nclass EquipmentType(Enum):\r\n    EXCAVATOR = \"excavator\"\r\n    CRANE = \"crane\"\r\n    LOADER = \"loader\"\r\n    BULLDOZER = \"bulldozer\"\r\n    DUMP_TRUCK = \"dump_truck\"\r\n    CONCRETE_MIXER = \"concrete_mixer\"\r\n    FORKLIFT = \"forklift\"\r\n    COMPACTOR = \"compactor\"\r\n    GRADER = \"grader\"\r\n    TELEHANDLER = \"telehandler\"\r\n\r\nclass OperatingStatus(Enum):\r\n    OPERATING = \"operating\"\r\n    IDLE = \"idle\"\r\n    OFF = \"off\"\r\n    MAINTENANCE = \"maintenance\"\r\n    FAULT = \"fault\"\r\n\r\nclass FaultSeverity(Enum):\r\n    INFO = \"info\"\r\n    WARNING = \"warning\"\r\n    CRITICAL = \"critical\"\r\n    SHUTDOWN = \"shutdown\"\r\n\r\n@dataclass\r\nclass GPSLocation:\r\n    latitude: float\r\n    longitude: float\r\n    altitude: float = 0.0\r\n    speed: float = 0.0\r\n    heading: float = 0.0\r\n    timestamp: datetime = field(default_factory=datetime.now)\r\n\r\n@dataclass\r\nclass TelematicsReading:\r\n    equipment_id: str\r\n    timestamp: datetime\r\n    location: GPSLocation\r\n    engine_hours: float\r\n    fuel_level: float  # Percentage\r\n    fuel_rate: float   # L\u002Fhr\r\n    engine_rpm: int\r\n    hydraulic_temp: float\r\n    coolant_temp: float\r\n    operating_status: OperatingStatus\r\n    load_percentage: float = 0.0\r\n    operator_id: str = \"\"\r\n\r\n@dataclass\r\nclass FaultCode:\r\n    code: str\r\n    description: str\r\n    severity: FaultSeverity\r\n    timestamp: datetime\r\n    equipment_id: str\r\n    resolved: bool = False\r\n\r\n@dataclass\r\nclass Equipment:\r\n    id: str\r\n    name: str\r\n    equipment_type: EquipmentType\r\n    make: str\r\n    model: str\r\n    year: int\r\n    serial_number: str\r\n    hourly_rate: float = 0.0\r\n    fuel_capacity: float = 0.0  # Liters\r\n    current_hours: float = 0.0\r\n    next_service_hours: float = 0.0\r\n    assigned_site: str = \"\"\r\n    assigned_operator: str = \"\"\r\n\r\n@dataclass\r\nclass Geofence:\r\n    id: str\r\n    name: str\r\n    center_lat: float\r\n    center_lon: float\r\n    radius_meters: float\r\n    allowed_equipment: List[str] = field(default_factory=list)\r\n\r\n@dataclass\r\nclass UtilizationReport:\r\n    equipment_id: str\r\n    period_start: datetime\r\n    period_end: datetime\r\n    total_hours: float\r\n    operating_hours: float\r\n    idle_hours: float\r\n    off_hours: float\r\n    utilization_pct: float\r\n    idle_pct: float\r\n    fuel_consumed: float\r\n    fuel_efficiency: float  # L\u002Foperating hour\r\n    cycles: int\r\n\r\nclass EquipmentTelematics:\r\n    \"\"\"Integrate and analyze equipment telematics data.\"\"\"\r\n\r\n    # Maintenance intervals by type (hours)\r\n    SERVICE_INTERVALS = {\r\n        EquipmentType.EXCAVATOR: 250,\r\n        EquipmentType.CRANE: 200,\r\n        EquipmentType.LOADER: 250,\r\n        EquipmentType.BULLDOZER: 250,\r\n        EquipmentType.DUMP_TRUCK: 300,\r\n    }\r\n\r\n    # Typical fuel rates (L\u002Fhr)\r\n    TYPICAL_FUEL_RATES = {\r\n        EquipmentType.EXCAVATOR: 15,\r\n        EquipmentType.CRANE: 12,\r\n        EquipmentType.LOADER: 18,\r\n        EquipmentType.BULLDOZER: 25,\r\n        EquipmentType.DUMP_TRUCK: 20,\r\n    }\r\n\r\n    def __init__(self, fleet_name: str):\r\n        self.fleet_name = fleet_name\r\n        self.equipment: Dict[str, Equipment] = {}\r\n        self.readings: List[TelematicsReading] = []\r\n        self.faults: List[FaultCode] = []\r\n        self.geofences: Dict[str, Geofence] = {}\r\n\r\n    def register_equipment(self, id: str, name: str, equipment_type: EquipmentType,\r\n                          make: str, model: str, year: int, serial_number: str,\r\n                          hourly_rate: float = 0, fuel_capacity: float = 0) -> Equipment:\r\n        \"\"\"Register equipment in fleet.\"\"\"\r\n        equipment = Equipment(\r\n            id=id,\r\n            name=name,\r\n            equipment_type=equipment_type,\r\n            make=make,\r\n            model=model,\r\n            year=year,\r\n            serial_number=serial_number,\r\n            hourly_rate=hourly_rate,\r\n            fuel_capacity=fuel_capacity\r\n        )\r\n        self.equipment[id] = equipment\r\n        return equipment\r\n\r\n    def add_geofence(self, id: str, name: str, center_lat: float,\r\n                    center_lon: float, radius_meters: float,\r\n                    allowed_equipment: List[str] = None) -> Geofence:\r\n        \"\"\"Add geofence boundary.\"\"\"\r\n        geofence = Geofence(\r\n            id=id,\r\n            name=name,\r\n            center_lat=center_lat,\r\n            center_lon=center_lon,\r\n            radius_meters=radius_meters,\r\n            allowed_equipment=allowed_equipment or []\r\n        )\r\n        self.geofences[id] = geofence\r\n        return geofence\r\n\r\n    def ingest_reading(self, equipment_id: str, location: GPSLocation,\r\n                      engine_hours: float, fuel_level: float, fuel_rate: float,\r\n                      engine_rpm: int, hydraulic_temp: float, coolant_temp: float,\r\n                      load_percentage: float = 0, operator_id: str = \"\") -> TelematicsReading:\r\n        \"\"\"Ingest telematics reading from equipment.\"\"\"\r\n        if equipment_id not in self.equipment:\r\n            raise ValueError(f\"Unknown equipment: {equipment_id}\")\r\n\r\n        # Determine operating status\r\n        if engine_rpm == 0:\r\n            status = OperatingStatus.OFF\r\n        elif engine_rpm \u003C 800 or load_percentage \u003C 10:\r\n            status = OperatingStatus.IDLE\r\n        else:\r\n            status = OperatingStatus.OPERATING\r\n\r\n        reading = TelematicsReading(\r\n            equipment_id=equipment_id,\r\n            timestamp=location.timestamp,\r\n            location=location,\r\n            engine_hours=engine_hours,\r\n            fuel_level=fuel_level,\r\n            fuel_rate=fuel_rate,\r\n            engine_rpm=engine_rpm,\r\n            hydraulic_temp=hydraulic_temp,\r\n            coolant_temp=coolant_temp,\r\n            operating_status=status,\r\n            load_percentage=load_percentage,\r\n            operator_id=operator_id\r\n        )\r\n\r\n        self.readings.append(reading)\r\n\r\n        # Update equipment status\r\n        equip = self.equipment[equipment_id]\r\n        equip.current_hours = engine_hours\r\n\r\n        # Check for issues\r\n        self._check_diagnostics(equipment_id, reading)\r\n        self._check_geofence(equipment_id, location)\r\n\r\n        return reading\r\n\r\n    def _check_diagnostics(self, equipment_id: str, reading: TelematicsReading):\r\n        \"\"\"Check for diagnostic issues.\"\"\"\r\n        equip = self.equipment[equipment_id]\r\n\r\n        # High temperature warning\r\n        if reading.hydraulic_temp > 90:\r\n            self._add_fault(equipment_id, \"HYD_TEMP_HIGH\",\r\n                          \"Hydraulic temperature high\", FaultSeverity.WARNING)\r\n\r\n        if reading.coolant_temp > 100:\r\n            self._add_fault(equipment_id, \"COOLANT_TEMP_HIGH\",\r\n                          \"Coolant temperature critical\", FaultSeverity.CRITICAL)\r\n\r\n        # Low fuel warning\r\n        if reading.fuel_level \u003C 15:\r\n            self._add_fault(equipment_id, \"FUEL_LOW\",\r\n                          \"Fuel level below 15%\", FaultSeverity.WARNING)\r\n\r\n        # Service due\r\n        service_interval = self.SERVICE_INTERVALS.get(equip.equipment_type, 250)\r\n        hours_to_service = equip.next_service_hours - reading.engine_hours\r\n\r\n        if hours_to_service \u003C 0:\r\n            self._add_fault(equipment_id, \"SERVICE_OVERDUE\",\r\n                          \"Maintenance service overdue\", FaultSeverity.WARNING)\r\n        elif hours_to_service \u003C 50:\r\n            self._add_fault(equipment_id, \"SERVICE_DUE\",\r\n                          f\"Service due in {hours_to_service:.0f} hours\", FaultSeverity.INFO)\r\n\r\n    def _check_geofence(self, equipment_id: str, location: GPSLocation):\r\n        \"\"\"Check geofence violations.\"\"\"\r\n        for geofence in self.geofences.values():\r\n            # Calculate distance from center\r\n            distance = self._haversine_distance(\r\n                location.latitude, location.longitude,\r\n                geofence.center_lat, geofence.center_lon\r\n            )\r\n\r\n            if distance > geofence.radius_meters:\r\n                if (not geofence.allowed_equipment or\r\n                    equipment_id in geofence.allowed_equipment):\r\n                    self._add_fault(equipment_id, \"GEOFENCE_EXIT\",\r\n                                  f\"Equipment left {geofence.name} boundary\",\r\n                                  FaultSeverity.WARNING)\r\n\r\n    def _haversine_distance(self, lat1: float, lon1: float,\r\n                           lat2: float, lon2: float) -> float:\r\n        \"\"\"Calculate distance between two coordinates in meters.\"\"\"\r\n        R = 6371000  # Earth radius in meters\r\n\r\n        phi1 = math.radians(lat1)\r\n        phi2 = math.radians(lat2)\r\n        delta_phi = math.radians(lat2 - lat1)\r\n        delta_lambda = math.radians(lon2 - lon1)\r\n\r\n        a = (math.sin(delta_phi\u002F2)**2 +\r\n             math.cos(phi1) * math.cos(phi2) * math.sin(delta_lambda\u002F2)**2)\r\n        c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))\r\n\r\n        return R * c\r\n\r\n    def _add_fault(self, equipment_id: str, code: str,\r\n                  description: str, severity: FaultSeverity):\r\n        \"\"\"Add fault code.\"\"\"\r\n        # Check if same fault already active\r\n        existing = [f for f in self.faults\r\n                   if f.equipment_id == equipment_id\r\n                   and f.code == code\r\n                   and not f.resolved]\r\n        if existing:\r\n            return\r\n\r\n        fault = FaultCode(\r\n            code=code,\r\n            description=description,\r\n            severity=severity,\r\n            timestamp=datetime.now(),\r\n            equipment_id=equipment_id\r\n        )\r\n        self.faults.append(fault)\r\n\r\n    def get_current_status(self, equipment_id: str) -> Dict:\r\n        \"\"\"Get current status of equipment.\"\"\"\r\n        if equipment_id not in self.equipment:\r\n            raise ValueError(f\"Unknown equipment: {equipment_id}\")\r\n\r\n        equip = self.equipment[equipment_id]\r\n\r\n        # Get latest reading\r\n        readings = [r for r in self.readings if r.equipment_id == equipment_id]\r\n        if not readings:\r\n            return {\"equipment\": equip, \"status\": \"no_data\"}\r\n\r\n        latest = max(readings, key=lambda r: r.timestamp)\r\n\r\n        # Active faults\r\n        active_faults = [f for f in self.faults\r\n                        if f.equipment_id == equipment_id and not f.resolved]\r\n\r\n        return {\r\n            \"equipment_id\": equip.id,\r\n            \"name\": equip.name,\r\n            \"type\": equip.equipment_type.value,\r\n            \"status\": latest.operating_status.value,\r\n            \"location\": {\r\n                \"lat\": latest.location.latitude,\r\n                \"lon\": latest.location.longitude,\r\n                \"speed\": latest.location.speed\r\n            },\r\n            \"engine_hours\": latest.engine_hours,\r\n            \"fuel_level\": latest.fuel_level,\r\n            \"fuel_rate\": latest.fuel_rate,\r\n            \"temps\": {\r\n                \"hydraulic\": latest.hydraulic_temp,\r\n                \"coolant\": latest.coolant_temp\r\n            },\r\n            \"operator\": latest.operator_id,\r\n            \"active_faults\": len(active_faults),\r\n            \"last_update\": latest.timestamp\r\n        }\r\n\r\n    def calculate_utilization(self, equipment_id: str,\r\n                             start_date: datetime,\r\n                             end_date: datetime) -> UtilizationReport:\r\n        \"\"\"Calculate utilization metrics for equipment.\"\"\"\r\n        readings = [r for r in self.readings\r\n                   if r.equipment_id == equipment_id\r\n                   and start_date \u003C= r.timestamp \u003C= end_date]\r\n\r\n        if not readings:\r\n            return None\r\n\r\n        readings.sort(key=lambda r: r.timestamp)\r\n\r\n        total_hours = (end_date - start_date).total_seconds() \u002F 3600\r\n        operating_hours = 0\r\n        idle_hours = 0\r\n        fuel_consumed = 0\r\n\r\n        # Calculate from readings\r\n        for i in range(1, len(readings)):\r\n            prev = readings[i-1]\r\n            curr = readings[i]\r\n\r\n            interval_hours = (curr.timestamp - prev.timestamp).total_seconds() \u002F 3600\r\n\r\n            if prev.operating_status == OperatingStatus.OPERATING:\r\n                operating_hours += interval_hours\r\n                fuel_consumed += prev.fuel_rate * interval_hours\r\n            elif prev.operating_status == OperatingStatus.IDLE:\r\n                idle_hours += interval_hours\r\n                fuel_consumed += prev.fuel_rate * interval_hours * 0.3  # Idle uses ~30% fuel\r\n\r\n        off_hours = total_hours - operating_hours - idle_hours\r\n        utilization_pct = (operating_hours \u002F total_hours * 100) if total_hours > 0 else 0\r\n        idle_pct = (idle_hours \u002F (operating_hours + idle_hours) * 100) if (operating_hours + idle_hours) > 0 else 0\r\n        fuel_efficiency = (fuel_consumed \u002F operating_hours) if operating_hours > 0 else 0\r\n\r\n        return UtilizationReport(\r\n            equipment_id=equipment_id,\r\n            period_start=start_date,\r\n            period_end=end_date,\r\n            total_hours=total_hours,\r\n            operating_hours=operating_hours,\r\n            idle_hours=idle_hours,\r\n            off_hours=off_hours,\r\n            utilization_pct=utilization_pct,\r\n            idle_pct=idle_pct,\r\n            fuel_consumed=fuel_consumed,\r\n            fuel_efficiency=fuel_efficiency,\r\n            cycles=0  # Would need load cycle detection\r\n        )\r\n\r\n    def get_fleet_summary(self) -> Dict:\r\n        \"\"\"Get summary of entire fleet.\"\"\"\r\n        summary = {\r\n            \"total_equipment\": len(self.equipment),\r\n            \"by_status\": {},\r\n            \"by_type\": {},\r\n            \"active_faults\": 0,\r\n            \"service_due\": []\r\n        }\r\n\r\n        for equip in self.equipment.values():\r\n            # Count by type\r\n            eq_type = equip.equipment_type.value\r\n            summary[\"by_type\"][eq_type] = summary[\"by_type\"].get(eq_type, 0) + 1\r\n\r\n            # Get current status\r\n            try:\r\n                status = self.get_current_status(equip.id)\r\n                op_status = status.get(\"status\", \"unknown\")\r\n                summary[\"by_status\"][op_status] = summary[\"by_status\"].get(op_status, 0) + 1\r\n\r\n                # Check service due\r\n                service_interval = self.SERVICE_INTERVALS.get(equip.equipment_type, 250)\r\n                hours_to_service = equip.next_service_hours - equip.current_hours\r\n                if hours_to_service \u003C 50:\r\n                    summary[\"service_due\"].append({\r\n                        \"equipment\": equip.name,\r\n                        \"hours_remaining\": hours_to_service\r\n                    })\r\n            except Exception:\r\n                summary[\"by_status\"][\"unknown\"] = summary[\"by_status\"].get(\"unknown\", 0) + 1\r\n\r\n        # Count active faults\r\n        summary[\"active_faults\"] = len([f for f in self.faults if not f.resolved])\r\n\r\n        return summary\r\n\r\n    def predict_maintenance(self, equipment_id: str) -> Dict:\r\n        \"\"\"Predict maintenance needs based on usage patterns.\"\"\"\r\n        if equipment_id not in self.equipment:\r\n            raise ValueError(f\"Unknown equipment: {equipment_id}\")\r\n\r\n        equip = self.equipment[equipment_id]\r\n\r\n        # Calculate average daily hours\r\n        week_ago = datetime.now() - timedelta(days=7)\r\n        recent_readings = [r for r in self.readings\r\n                         if r.equipment_id == equipment_id\r\n                         and r.timestamp > week_ago]\r\n\r\n        if len(recent_readings) \u003C 2:\r\n            return {\"prediction\": \"insufficient_data\"}\r\n\r\n        hours_start = min(r.engine_hours for r in recent_readings)\r\n        hours_end = max(r.engine_hours for r in recent_readings)\r\n        days = (max(r.timestamp for r in recent_readings) -\r\n                min(r.timestamp for r in recent_readings)).days or 1\r\n\r\n        daily_hours = (hours_end - hours_start) \u002F days\r\n\r\n        # Predict service date\r\n        service_interval = self.SERVICE_INTERVALS.get(equip.equipment_type, 250)\r\n        hours_to_service = equip.next_service_hours - equip.current_hours\r\n\r\n        if daily_hours > 0:\r\n            days_to_service = hours_to_service \u002F daily_hours\r\n            service_date = datetime.now() + timedelta(days=days_to_service)\r\n        else:\r\n            service_date = None\r\n\r\n        return {\r\n            \"equipment_id\": equipment_id,\r\n            \"current_hours\": equip.current_hours,\r\n            \"next_service_hours\": equip.next_service_hours,\r\n            \"hours_to_service\": hours_to_service,\r\n            \"avg_daily_hours\": daily_hours,\r\n            \"predicted_service_date\": service_date,\r\n            \"service_type\": \"Routine maintenance\",\r\n            \"estimated_downtime_hours\": 8\r\n        }\r\n\r\n    def generate_report(self) -> str:\r\n        \"\"\"Generate fleet telematics report.\"\"\"\r\n        summary = self.get_fleet_summary()\r\n\r\n        lines = [\r\n            \"# Equipment Telematics Report\",\r\n            \"\",\r\n            f\"**Fleet:** {self.fleet_name}\",\r\n            f\"**Report Date:** {datetime.now().strftime('%Y-%m-%d %H:%M')}\",\r\n            \"\",\r\n            \"## Fleet Summary\",\r\n            \"\",\r\n            f\"| Metric | Value |\",\r\n            f\"|--------|-------|\",\r\n            f\"| Total Equipment | {summary['total_equipment']} |\",\r\n            f\"| Active Faults | {summary['active_faults']} |\",\r\n            f\"| Service Due | {len(summary['service_due'])} |\",\r\n            \"\",\r\n            \"## Status Distribution\",\r\n            \"\"\r\n        ]\r\n\r\n        for status, count in summary[\"by_status\"].items():\r\n            lines.append(f\"- {status}: {count}\")\r\n\r\n        # Equipment details\r\n        lines.extend([\r\n            \"\",\r\n            \"## Equipment Status\",\r\n            \"\",\r\n            \"| Equipment | Type | Status | Hours | Fuel | Faults |\",\r\n            \"|-----------|------|--------|-------|------|--------|\"\r\n        ])\r\n\r\n        for equip in self.equipment.values():\r\n            try:\r\n                status = self.get_current_status(equip.id)\r\n                status_icon = \"✅\" if status['status'] == 'operating' else \"⏸️\" if status['status'] == 'idle' else \"⏹️\"\r\n                lines.append(\r\n                    f\"| {equip.name} | {equip.equipment_type.value} | \"\r\n                    f\"{status_icon} {status['status']} | {status['engine_hours']:.0f} | \"\r\n                    f\"{status['fuel_level']:.0f}% | {status['active_faults']} |\"\r\n                )\r\n            except Exception:\r\n                lines.append(\r\n                    f\"| {equip.name} | {equip.equipment_type.value} | ⚠️ No data | - | - | - |\"\r\n                )\r\n\r\n        # Service due\r\n        if summary[\"service_due\"]:\r\n            lines.extend([\r\n                \"\",\r\n                \"## Service Due Soon\",\r\n                \"\",\r\n                \"| Equipment | Hours Remaining |\",\r\n                \"|-----------|-----------------|\"\r\n            ])\r\n            for svc in summary[\"service_due\"]:\r\n                lines.append(f\"| {svc['equipment']} | {svc['hours_remaining']:.0f} |\")\r\n\r\n        # Active faults\r\n        active_faults = [f for f in self.faults if not f.resolved]\r\n        if active_faults:\r\n            lines.extend([\r\n                \"\",\r\n                \"## Active Faults\",\r\n                \"\",\r\n                \"| Equipment | Code | Description | Severity |\",\r\n                \"|-----------|------|-------------|----------|\"\r\n            ])\r\n            for fault in active_faults[:10]:\r\n                sev_icon = \"?\" if fault.severity == FaultSeverity.CRITICAL else \"?\"\r\n                equip = self.equipment.get(fault.equipment_id)\r\n                lines.append(\r\n                    f\"| {equip.name if equip else fault.equipment_id} | \"\r\n                    f\"{fault.code} | {fault.description} | {sev_icon} {fault.severity.value} |\"\r\n                )\r\n\r\n        return \"\\n\".join(lines)\r\n```\r\n\r\n## Quick Start\r\n\r\n```python\r\nfrom datetime import datetime, timedelta\r\n\r\n# Initialize telematics system\r\ntelematics = EquipmentTelematics(\"Site A Fleet\")\r\n\r\n# Register equipment\r\ntelematics.register_equipment(\r\n    \"EX-001\", \"Excavator #1\", EquipmentType.EXCAVATOR,\r\n    make=\"Caterpillar\", model=\"320\", year=2022,\r\n    serial_number=\"CAT320X12345\",\r\n    hourly_rate=150, fuel_capacity=400\r\n)\r\n\r\ntelematics.register_equipment(\r\n    \"CR-001\", \"Tower Crane #1\", EquipmentType.CRANE,\r\n    make=\"Liebherr\", model=\"200EC-H\", year=2021,\r\n    serial_number=\"LH200EC54321\",\r\n    hourly_rate=200, fuel_capacity=300\r\n)\r\n\r\n# Add geofence for site boundary\r\ntelematics.add_geofence(\r\n    \"SITE-A\", \"Site A Boundary\",\r\n    center_lat=40.7128, center_lon=-74.0060,\r\n    radius_meters=500\r\n)\r\n\r\n# Ingest telematics reading\r\nlocation = GPSLocation(\r\n    latitude=40.7128, longitude=-74.0059,\r\n    speed=5.0, timestamp=datetime.now()\r\n)\r\n\r\ntelematics.ingest_reading(\r\n    \"EX-001\", location,\r\n    engine_hours=1250.5,\r\n    fuel_level=65.0,\r\n    fuel_rate=18.5,\r\n    engine_rpm=1800,\r\n    hydraulic_temp=75.0,\r\n    coolant_temp=85.0,\r\n    load_percentage=75,\r\n    operator_id=\"OP-101\"\r\n)\r\n\r\n# Get current status\r\nstatus = telematics.get_current_status(\"EX-001\")\r\nprint(f\"Excavator status: {status['status']}\")\r\nprint(f\"Location: {status['location']}\")\r\nprint(f\"Fuel: {status['fuel_level']}%\")\r\n\r\n# Calculate utilization\r\nutil = telematics.calculate_utilization(\r\n    \"EX-001\",\r\n    datetime.now() - timedelta(days=7),\r\n    datetime.now()\r\n)\r\nif util:\r\n    print(f\"Utilization: {util.utilization_pct:.1f}%\")\r\n    print(f\"Fuel efficiency: {util.fuel_efficiency:.1f} L\u002Fhr\")\r\n\r\n# Predict maintenance\r\nmaintenance = telematics.predict_maintenance(\"EX-001\")\r\nprint(f\"Days to service: {maintenance.get('predicted_service_date')}\")\r\n\r\n# Fleet summary\r\nsummary = telematics.get_fleet_summary()\r\nprint(f\"Fleet: {summary['total_equipment']} units\")\r\n\r\n# Generate report\r\nprint(telematics.generate_report())\r\n```\r\n\r\n## Requirements\r\n\r\n```bash\r\npip install (no external dependencies)\r\n```\r\n","# Equipment Telematics\r\n\r\n## Overview\r\n\r\nIntegrate telematics data from heavy construction equipment (excavators, cranes, loaders, trucks) to monitor utilization, track location, analyze fuel efficiency, predict maintenance needs, and ensure safe operation.\r\n\r\n## Telematics Data Flow\r\n\r\n```\r\n┌─────────────────────────────────────────────────────────────────┐\r\n│                  EQUIPMENT TELEMATICS                            │\r\n├─────────────────────────────────────────────────────────────────┤\r\n│                                                                  │\r\n│  EQUIPMENT                  TELEMATICS              ANALYTICS   │\r\n│  ─────────                  ──────────              ─────────   │\r\n│                                                                  │\r\n│  ? Excavator  ────┐       ? Location              ? Utilization│\r\n│  ?️ Crane      ────┼──────→ ? Engine Hours ────────→ ⛽ Fuel      │\r\n│  ? Truck      ────┤       ⛽ Fuel Level             ? Maintenance│\r\n│  ? Loader     ────┘       ⚡ Performance            ? Operator   │\r\n│                                                                  │\r\n│  METRICS TRACKED:                                               │\r\n│  • GPS location and geofencing                                  │\r\n│  • Engine hours and idle time                                   │\r\n│  • Fuel consumption rate                                        │\r\n│  • Load cycles and productivity                                 │\r\n│  • Fault codes and diagnostics                                  │\r\n│  • Operator behavior and safety                                 │\r\n│                                                                  │\r\n└─────────────────────────────────────────────────────────────────┘\r\n```\r\n\r\n## Technical Implementation\r\n\r\n```python\r\nfrom dataclasses import dataclass, field\r\nfrom typing import List, Dict, Optional, Tuple\r\nfrom datetime import datetime, timedelta\r\nfrom enum import Enum\r\nimport statistics\r\nimport math\r\n\r\nclass EquipmentType(Enum):\r\n    EXCAVATOR = \"excavator\"\r\n    CRANE = \"crane\"\r\n    LOADER = \"loader\"\r\n    BULLDOZER = \"bulldozer\"\r\n    DUMP_TRUCK = \"dump_truck\"\r\n    CONCRETE_MIXER = \"concrete_mixer\"\r\n    FORKLIFT = \"forklift\"\r\n    COMPACTOR = \"compactor\"\r\n    GRADER = \"grader\"\r\n    TELEHANDLER = \"telehandler\"\r\n\r\nclass OperatingStatus(Enum):\r\n    OPERATING = \"operating\"\r\n    IDLE = \"idle\"\r\n    OFF = \"off\"\r\n    MAINTENANCE = \"maintenance\"\r\n    FAULT = \"fault\"\r\n\r\nclass FaultSeverity(Enum):\r\n    INFO = \"info\"\r\n    WARNING = \"warning\"\r\n    CRITICAL = \"critical\"\r\n    SHUTDOWN = \"shutdown\"\r\n\r\n@dataclass\r\nclass GPSLocation:\r\n    latitude: float\r\n    longitude: float\r\n    altitude: float = 0.0\r\n    speed: float = 0.0\r\n    heading: float = 0.0\r\n    timestamp: datetime = field(default_factory=datetime.now)\r\n\r\n@dataclass\r\nclass TelematicsReading:\r\n    equipment_id: str\r\n    timestamp: datetime\r\n    location: GPSLocation\r\n    engine_hours: float\r\n    fuel_level: float  # Percentage\r\n    fuel_rate: float   # L\u002Fhr\r\n    engine_rpm: int\r\n    hydraulic_temp: float\r\n    coolant_temp: float\r\n    operating_status: OperatingStatus\r\n    load_percentage: float = 0.0\r\n    operator_id: str = \"\"\r\n\r\n@dataclass\r\nclass FaultCode:\r\n    code: str\r\n    description: str\r\n    severity: FaultSeverity\r\n    timestamp: datetime\r\n    equipment_id: str\r\n    resolved: bool = False\r\n\r\n@dataclass\r\nclass Equipment:\r\n    id: str\r\n    name: str\r\n    equipment_type: EquipmentType\r\n    make: str\r\n    model: str\r\n    year: int\r\n    serial_number: str\r\n    hourly_rate: float = 0.0\r\n    fuel_capacity: float = 0.0  # Liters\r\n    current_hours: float = 0.0\r\n    next_service_hours: float = 0.0\r\n    assigned_site: str = \"\"\r\n    assigned_operator: str = \"\"\r\n\r\n@dataclass\r\nclass Geofence:\r\n    id: str\r\n    name: str\r\n    center_lat: float\r\n    center_lon: float\r\n    radius_meters: float\r\n    allowed_equipment: List[str] = field(default_factory=list)\r\n\r\n@dataclass\r\nclass UtilizationReport:\r\n    equipment_id: str\r\n    period_start: datetime\r\n    period_end: datetime\r\n    total_hours: float\r\n    operating_hours: float\r\n    idle_hours: float\r\n    off_hours: float\r\n    utilization_pct: float\r\n    idle_pct: float\r\n    fuel_consumed: float\r\n    fuel_efficiency: float  # L\u002Foperating hour\r\n    cycles: int\r\n\r\nclass EquipmentTelematics:\r\n    \"\"\"Integrate and analyze equipment telematics data.\"\"\"\r\n\r\n    # Maintenance intervals by type (hours)\r\n    SERVICE_INTERVALS = {\r\n        EquipmentType.EXCAVATOR: 250,\r\n        EquipmentType.CRANE: 200,\r\n        EquipmentType.LOADER: 250,\r\n        EquipmentType.BULLDOZER: 250,\r\n        EquipmentType.DUMP_TRUCK: 300,\r\n    }\r\n\r\n    # Typical fuel rates (L\u002Fhr)\r\n    TYPICAL_FUEL_RATES = {\r\n        EquipmentType.EXCAVATOR: 15,\r\n        EquipmentType.CRANE: 12,\r\n        EquipmentType.LOADER: 18,\r\n        EquipmentType.BULLDOZER: 25,\r\n        EquipmentType.DUMP_TRUCK: 20,\r\n    }\r\n\r\n    def __init__(self, fleet_name: str):\r\n        self.fleet_name = fleet_name\r\n        self.equipment: Dict[str, Equipment] = {}\r\n        self.readings: List[TelematicsReading] = []\r\n        self.faults: List[FaultCode] = []\r\n        self.geofences: Dict[str, Geofence] = {}\r\n\r\n    def register_equipment(self, id: str, name: str, equipment_type: EquipmentType,\r\n                          make: str, model: str, year: int, serial_number: str,\r\n                          hourly_rate: float = 0, fuel_capacity: float = 0) -> Equipment:\r\n        \"\"\"Register equipment in fleet.\"\"\"\r\n        equipment = Equipment(\r\n            id=id,\r\n            name=name,\r\n            equipment_type=equipment_type,\r\n            make=make,\r\n            model=model,\r\n            year=year,\r\n            serial_number=serial_number,\r\n            hourly_rate=hourly_rate,\r\n            fuel_capacity=fuel_capacity\r\n        )\r\n        self.equipment[id] = equipment\r\n        return equipment\r\n\r\n    def add_geofence(self, id: str, name: str, center_lat: float,\r\n                    center_lon: float, radius_meters: float,\r\n                    allowed_equipment: List[str] = None) -> Geofence:\r\n        \"\"\"Add geofence boundary.\"\"\"\r\n        geofence = Geofence(\r\n            id=id,\r\n            name=name,\r\n            center_lat=center_lat,\r\n            center_lon=center_lon,\r\n            radius_meters=radius_meters,\r\n            allowed_equipment=allowed_equipment or []\r\n        )\r\n        self.geofences[id] = geofence\r\n        return geofence\r\n\r\n    def ingest_reading(self, equipment_id: str, location: GPSLocation,\r\n                      engine_hours: float, fuel_level: float, fuel_rate: float,\r\n                      engine_rpm: int, hydraulic_temp: float, coolant_temp: float,\r\n                      load_percentage: float = 0, operator_id: str = \"\") -> TelematicsReading:\r\n        \"\"\"Ingest telematics reading from equipment.\"\"\"\r\n        if equipment_id not in self.equipment:\r\n            raise ValueError(f\"Unknown equipment: {equipment_id}\")\r\n\r\n        # Determine operating status\r\n        if engine_rpm == 0:\r\n            status = OperatingStatus.OFF\r\n        elif engine_rpm \u003C 800 or load_percentage \u003C 10:\r\n            status = OperatingStatus.IDLE\r\n        else:\r\n            status = OperatingStatus.OPERATING\r\n\r\n        reading = TelematicsReading(\r\n            equipment_id=equipment_id,\r\n            timestamp=location.timestamp,\r\n            location=location,\r\n            engine_hours=engine_hours,\r\n            fuel_level=fuel_level,\r\n            fuel_rate=fuel_rate,\r\n            engine_rpm=engine_rpm,\r\n            hydraulic_temp=hydraulic_temp,\r\n            coolant_temp=coolant_temp,\r\n            operating_status=status,\r\n            load_percentage=load_percentage,\r\n            operator_id=operator_id\r\n        )\r\n\r\n        self.readings.append(reading)\r\n\r\n        # Update equipment status\r\n        equip = self.equipment[equipment_id]\r\n        equip.current_hours = engine_hours\r\n\r\n        # Check for issues\r\n        self._check_diagnostics(equipment_id, reading)\r\n        self._check_geofence(equipment_id, location)\r\n\r\n        return reading\r\n\r\n    def _check_diagnostics(self, equipment_id: str, reading: TelematicsReading):\r\n        \"\"\"Check for diagnostic issues.\"\"\"\r\n        equip = self.equipment[equipment_id]\r\n\r\n        # High temperature warning\r\n        if reading.hydraulic_temp > 90:\r\n            self._add_fault(equipment_id, \"HYD_TEMP_HIGH\",\r\n                          \"Hydraulic temperature high\", FaultSeverity.WARNING)\r\n\r\n        if reading.coolant_temp > 100:\r\n            self._add_fault(equipment_id, \"COOLANT_TEMP_HIGH\",\r\n                          \"Coolant temperature critical\", FaultSeverity.CRITICAL)\r\n\r\n        # Low fuel warning\r\n        if reading.fuel_level \u003C 15:\r\n            self._add_fault(equipment_id, \"FUEL_LOW\",\r\n                          \"Fuel level below 15%\", FaultSeverity.WARNING)\r\n\r\n        # Service due\r\n        service_interval = self.SERVICE_INTERVALS.get(equip.equipment_type, 250)\r\n        hours_to_service = equip.next_service_hours - reading.engine_hours\r\n\r\n        if hours_to_service \u003C 0:\r\n            self._add_fault(equipment_id, \"SERVICE_OVERDUE\",\r\n                          \"Maintenance service overdue\", FaultSeverity.WARNING)\r\n        elif hours_to_service \u003C 50:\r\n            self._add_fault(equipment_id, \"SERVICE_DUE\",\r\n                          f\"Service due in {hours_to_service:.0f} hours\", FaultSeverity.INFO)\r\n\r\n    def _check_geofence(self, equipment_id: str, location: GPSLocation):\r\n        \"\"\"Check geofence violations.\"\"\"\r\n        for geofence in self.geofences.values():\r\n            # Calculate distance from center\r\n            distance = self._haversine_distance(\r\n                location.latitude, location.longitude,\r\n                geofence.center_lat, geofence.center_lon\r\n            )\r\n\r\n            if distance > geofence.radius_meters:\r\n                if (not geofence.allowed_equipment or\r\n                    equipment_id in geofence.allowed_equipment):\r\n                    self._add_fault(equipment_id, \"GEOFENCE_EXIT\",\r\n                                  f\"Equipment left {geofence.name} boundary\",\r\n                                  FaultSeverity.WARNING)\r\n\r\n    def _haversine_distance(self, lat1: float, lon1: float,\r\n                           lat2: float, lon2: float) -> float:\r\n        \"\"\"Calculate distance between two coordinates in meters.\"\"\"\r\n        R = 6371000  # Earth radius in meters\r\n\r\n        phi1 = math.radians(lat1)\r\n        phi2 = math.radians(lat2)\r\n        delta_phi = math.radians(lat2 - lat1)\r\n        delta_lambda = math.radians(lon2 - lon1)\r\n\r\n        a = (math.sin(delta_phi\u002F2)**2 +\r\n             math.cos(phi1) * math.cos(phi2) * math.sin(delta_lambda\u002F2)**2)\r\n        c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))\r\n\r\n        return R * c\r\n\r\n    def _add_fault(self, equipment_id: str, code: str,\r\n                  description: str, severity: FaultSeverity):\r\n        \"\"\"Add fault code.\"\"\"\r\n        # Check if same fault already active\r\n        existing = [f for f in self.faults\r\n                   if f.equipment_id == equipment_id\r\n                   and f.code == code\r\n                   and not f.resolved]\r\n        if existing:\r\n            return\r\n\r\n        fault = FaultCode(\r\n            code=code,\r\n            description=description,\r\n            severity=severity,\r\n            timestamp=datetime.now(),\r\n            equipment_id=equipment_id\r\n        )\r\n        self.faults.append(fault)\r\n\r\n    def get_current_status(self, equipment_id: str) -> Dict:\r\n        \"\"\"Get current status of equipment.\"\"\"\r\n        if equipment_id not in self.equipment:\r\n            raise ValueError(f\"Unknown equipment: {equipment_id}\")\r\n\r\n        equip = self.equipment[equipment_id]\r\n\r\n        # Get latest reading\r\n        readings = [r for r in self.readings if r.equipment_id == equipment_id]\r\n        if not readings:\r\n            return {\"equipment\": equip, \"status\": \"no_data\"}\r\n\r\n        latest = max(readings, key=lambda r: r.timestamp)\r\n\r\n        # Active faults\r\n        active_faults = [f for f in self.faults\r\n                        if f.equipment_id == equipment_id and not f.resolved]\r\n\r\n        return {\r\n            \"equipment_id\": equip.id,\r\n            \"name\": equip.name,\r\n            \"type\": equip.equipment_type.value,\r\n            \"status\": latest.operating_status.value,\r\n            \"location\": {\r\n                \"lat\": latest.location.latitude,\r\n                \"lon\": latest.location.longitude,\r\n                \"speed\": latest.location.speed\r\n            },\r\n            \"engine_hours\": latest.engine_hours,\r\n            \"fuel_level\": latest.fuel_level,\r\n            \"fuel_rate\": latest.fuel_rate,\r\n            \"temps\": {\r\n                \"hydraulic\": latest.hydraulic_temp,\r\n                \"coolant\": latest.coolant_temp\r\n            },\r\n            \"operator\": latest.operator_id,\r\n            \"active_faults\": len(active_faults),\r\n            \"last_update\": latest.timestamp\r\n        }\r\n\r\n    def calculate_utilization(self, equipment_id: str,\r\n                             start_date: datetime,\r\n                             end_date: datetime) -> UtilizationReport:\r\n        \"\"\"Calculate utilization metrics for equipment.\"\"\"\r\n        readings = [r for r in self.readings\r\n                   if r.equipment_id == equipment_id\r\n                   and start_date \u003C= r.timestamp \u003C= end_date]\r\n\r\n        if not readings:\r\n            return None\r\n\r\n        readings.sort(key=lambda r: r.timestamp)\r\n\r\n        total_hours = (end_date - start_date).total_seconds() \u002F 3600\r\n        operating_hours = 0\r\n        idle_hours = 0\r\n        fuel_consumed = 0\r\n\r\n        # Calculate from readings\r\n        for i in range(1, len(readings)):\r\n            prev = readings[i-1]\r\n            curr = readings[i]\r\n\r\n            interval_hours = (curr.timestamp - prev.timestamp).total_seconds() \u002F 3600\r\n\r\n            if prev.operating_status == OperatingStatus.OPERATING:\r\n                operating_hours += interval_hours\r\n                fuel_consumed += prev.fuel_rate * interval_hours\r\n            elif prev.operating_status == OperatingStatus.IDLE:\r\n                idle_hours += interval_hours\r\n                fuel_consumed += prev.fuel_rate * interval_hours * 0.3  # Idle uses ~30% fuel\r\n\r\n        off_hours = total_hours - operating_hours - idle_hours\r\n        utilization_pct = (operating_hours \u002F total_hours * 100) if total_hours > 0 else 0\r\n        idle_pct = (idle_hours \u002F (operating_hours + idle_hours) * 100) if (operating_hours + idle_hours) > 0 else 0\r\n        fuel_efficiency = (fuel_consumed \u002F operating_hours) if operating_hours > 0 else 0\r\n\r\n        return UtilizationReport(\r\n            equipment_id=equipment_id,\r\n            period_start=start_date,\r\n            period_end=end_date,\r\n            total_hours=total_hours,\r\n            operating_hours=operating_hours,\r\n            idle_hours=idle_hours,\r\n            off_hours=off_hours,\r\n            utilization_pct=utilization_pct,\r\n            idle_pct=idle_pct,\r\n            fuel_consumed=fuel_consumed,\r\n            fuel_efficiency=fuel_efficiency,\r\n            cycles=0  # Would need load cycle detection\r\n        )\r\n\r\n    def get_fleet_summary(self) -> Dict:\r\n        \"\"\"Get summary of entire fleet.\"\"\"\r\n        summary = {\r\n            \"total_equipment\": len(self.equipment),\r\n            \"by_status\": {},\r\n            \"by_type\": {},\r\n            \"active_faults\": 0,\r\n            \"service_due\": []\r\n        }\r\n\r\n        for equip in self.equipment.values():\r\n            # Count by type\r\n            eq_type = equip.equipment_type.value\r\n            summary[\"by_type\"][eq_type] = summary[\"by_type\"].get(eq_type, 0) + 1\r\n\r\n            # Get current status\r\n            try:\r\n                status = self.get_current_status(equip.id)\r\n                op_status = status.get(\"status\", \"unknown\")\r\n                summary[\"by_status\"][op_status] = summary[\"by_status\"].get(op_status, 0) + 1\r\n\r\n                # Check service due\r\n                service_interval = self.SERVICE_INTERVALS.get(equip.equipment_type, 250)\r\n                hours_to_service = equip.next_service_hours - equip.current_hours\r\n                if hours_to_service \u003C 50:\r\n                    summary[\"service_due\"].append({\r\n                        \"equipment\": equip.name,\r\n                        \"hours_remaining\": hours_to_service\r\n                    })\r\n            except Exception:\r\n                summary[\"by_status\"][\"unknown\"] = summary[\"by_status\"].get(\"unknown\", 0) + 1\r\n\r\n        # Count active faults\r\n        summary[\"active_faults\"] = len([f for f in self.faults if not f.resolved])\r\n\r\n        return summary\r\n\r\n    def predict_maintenance(self, equipment_id: str) -> Dict:\r\n        \"\"\"Predict maintenance needs based on usage patterns.\"\"\"\r\n        if equipment_id not in self.equipment:\r\n            raise ValueError(f\"Unknown equipment: {equipment_id}\")\r\n\r\n        equip = self.equipment[equipment_id]\r\n\r\n        # Calculate average daily hours\r\n        week_ago = datetime.now() - timedelta(days=7)\r\n        recent_readings = [r for r in self.readings\r\n                         if r.equipment_id == equipment_id\r\n                         and r.timestamp > week_ago]\r\n\r\n        if len(recent_readings) \u003C 2:\r\n            return {\"prediction\": \"insufficient_data\"}\r\n\r\n        hours_start = min(r.engine_hours for r in recent_readings)\r\n        hours_end = max(r.engine_hours for r in recent_readings)\r\n        days = (max(r.timestamp for r in recent_readings) -\r\n                min(r.timestamp for r in recent_readings)).days or 1\r\n\r\n        daily_hours = (hours_end - hours_start) \u002F days\r\n\r\n        # Predict service date\r\n        service_interval = self.SERVICE_INTERVALS.get(equip.equipment_type, 250)\r\n        hours_to_service = equip.next_service_hours - equip.current_hours\r\n\r\n        if daily_hours > 0:\r\n            days_to_service = hours_to_service \u002F daily_hours\r\n            service_date = datetime.now() + timedelta(days=days_to_service)\r\n        else:\r\n            service_date = None\r\n\r\n        return {\r\n            \"equipment_id\": equipment_id,\r\n            \"current_hours\": equip.current_hours,\r\n            \"next_service_hours\": equip.next_service_hours,\r\n            \"hours_to_service\": hours_to_service,\r\n            \"avg_daily_hours\": daily_hours,\r\n            \"predicted_service_date\": service_date,\r\n            \"service_type\": \"Routine maintenance\",\r\n            \"estimated_downtime_hours\": 8\r\n        }\r\n\r\n    def generate_report(self) -> str:\r\n        \"\"\"Generate fleet telematics report.\"\"\"\r\n        summary = self.get_fleet_summary()\r\n\r\n        lines = [\r\n            \"# Equipment Telematics Report\",\r\n            \"\",\r\n            f\"**Fleet:** {self.fleet_name}\",\r\n            f\"**Report Date:** {datetime.now().strftime('%Y-%m-%d %H:%M')}\",\r\n            \"\",\r\n            \"## Fleet Summary\",\r\n            \"\",\r\n            f\"| Metric | Value |\",\r\n            f\"|--------|-------|\",\r\n            f\"| Total Equipment | {summary['total_equipment']} |\",\r\n            f\"| Active Faults | {summary['active_faults']} |\",\r\n            f\"| Service Due | {len(summary['service_due'])} |\",\r\n            \"\",\r\n            \"## Status Distribution\",\r\n            \"\"\r\n        ]\r\n\r\n        for status, count in summary[\"by_status\"].items():\r\n            lines.append(f\"- {status}: {count}\")\r\n\r\n        # Equipment details\r\n        lines.extend([\r\n            \"\",\r\n            \"## Equipment Status\",\r\n            \"\",\r\n            \"| Equipment | Type | Status | Hours | Fuel | Faults |\",\r\n            \"|-----------|------|--------|-------|------|--------|\"\r\n        ])\r\n\r\n        for equip in self.equipment.values():\r\n            try:\r\n                status = self.get_current_status(equip.id)\r\n                status_icon = \"✅\" if status['status'] == 'operating' else \"⏸️\" if status['status'] == 'idle' else \"⏹️\"\r\n                lines.append(\r\n                    f\"| {equip.name} | {equip.equipment_type.value} | \"\r\n                    f\"{status_icon} {status['status']} | {status['engine_hours']:.0f} | \"\r\n                    f\"{status['fuel_level']:.0f}% | {status['active_faults']} |\"\r\n                )\r\n            except Exception:\r\n                lines.append(\r\n                    f\"| {equip.name} | {equip.equipment_type.value} | ⚠️ No data | - | - | - |\"\r\n                )\r\n\r\n        # Service due\r\n        if summary[\"service_due\"]:\r\n            lines.extend([\r\n                \"\",\r\n                \"## Service Due Soon\",\r\n                \"\",\r\n                \"| Equipment | Hours Remaining |\",\r\n                \"|-----------|-----------------|\"\r\n            ])\r\n            for svc in summary[\"service_due\"]:\r\n                lines.append(f\"| {svc['equipment']} | {svc['hours_remaining']:.0f} |\")\r\n\r\n        # Active faults\r\n        active_faults = [f for f in self.faults if not f.resolved]\r\n        if active_faults:\r\n            lines.extend([\r\n                \"\",\r\n                \"## Active Faults\",\r\n                \"\",\r\n                \"| Equipment | Code | Description | Severity |\",\r\n                \"|-----------|------|-------------|----------|\"\r\n            ])\r\n            for fault in active_faults[:10]:\r\n                sev_icon = \"?\" if fault.severity == FaultSeverity.CRITICAL else \"?\"\r\n                equip = self.equipment.get(fault.equipment_id)\r\n                lines.append(\r\n                    f\"| {equip.name if equip else fault.equipment_id} | \"\r\n                    f\"{fault.code} | {fault.description} | {sev_icon} {fault.severity.value} |\"\r\n                )\r\n\r\n        return \"\\n\".join(lines)\r\n```\r\n\r\n## Quick Start\r\n\r\n```python\r\nfrom datetime import datetime, timedelta\r\n\r\n# Initialize telematics system\r\ntelematics = EquipmentTelematics(\"Site A Fleet\")\r\n\r\n# Register equipment\r\ntelematics.register_equipment(\r\n    \"EX-001\", \"Excavator #1\", EquipmentType.EXCAVATOR,\r\n    make=\"Caterpillar\", model=\"320\", year=2022,\r\n    serial_number=\"CAT320X12345\",\r\n    hourly_rate=150, fuel_capacity=400\r\n)\r\n\r\ntelematics.register_equipment(\r\n    \"CR-001\", \"Tower Crane #1\", EquipmentType.CRANE,\r\n    make=\"Liebherr\", model=\"200EC-H\", year=2021,\r\n    serial_number=\"LH200EC54321\",\r\n    hourly_rate=200, fuel_capacity=300\r\n)\r\n\r\n# Add geofence for site boundary\r\ntelematics.add_geofence(\r\n    \"SITE-A\", \"Site A Boundary\",\r\n    center_lat=40.7128, center_lon=-74.0060,\r\n    radius_meters=500\r\n)\r\n\r\n# Ingest telematics reading\r\nlocation = GPSLocation(\r\n    latitude=40.7128, longitude=-74.0059,\r\n    speed=5.0, timestamp=datetime.now()\r\n)\r\n\r\ntelematics.ingest_reading(\r\n    \"EX-001\", location,\r\n    engine_hours=1250.5,\r\n    fuel_level=65.0,\r\n    fuel_rate=18.5,\r\n    engine_rpm=1800,\r\n    hydraulic_temp=75.0,\r\n    coolant_temp=85.0,\r\n    load_percentage=75,\r\n    operator_id=\"OP-101\"\r\n)\r\n\r\n# Get current status\r\nstatus = telematics.get_current_status(\"EX-001\")\r\nprint(f\"Excavator status: {status['status']}\")\r\nprint(f\"Location: {status['location']}\")\r\nprint(f\"Fuel: {status['fuel_level']}%\")\r\n\r\n# Calculate utilization\r\nutil = telematics.calculate_utilization(\r\n    \"EX-001\",\r\n    datetime.now() - timedelta(days=7),\r\n    datetime.now()\r\n)\r\nif util:\r\n    print(f\"Utilization: {util.utilization_pct:.1f}%\")\r\n    print(f\"Fuel efficiency: {util.fuel_efficiency:.1f} L\u002Fhr\")\r\n\r\n# Predict maintenance\r\nmaintenance = telematics.predict_maintenance(\"EX-001\")\r\nprint(f\"Days to service: {maintenance.get('predicted_service_date')}\")\r\n\r\n# Fleet summary\r\nsummary = telematics.get_fleet_summary()\r\nprint(f\"Fleet: {summary['total_equipment']} units\")\r\n\r\n# Generate report\r\nprint(telematics.generate_report())\r\n```\r\n\r\n## Requirements\r\n\r\n```bash\r\npip install (no external dependencies)\r\n```\r\n",{"slug":5,"description":7,"display_name":32},"Equipment Telematics","main","e7e336ac628c3e3bd771aff1aaa469c93778f94a","skills\u002Fdatadrivenconstruction\u002Fequipment-telematics",0,"https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del","2026-08-23 22:39:37",{"source":8,"skillName":5,"commands":41,"prompt":51,"zipAvailable":52},[42,45,48],{"tool":43,"command":44},"npx","npx skills add kbarbel640-del\u002Fskills --skill equipment-telematics",{"tool":46,"command":47},"bunx","bunx skills add kbarbel640-del\u002Fskills --skill equipment-telematics",{"tool":49,"command":50},"pnpm","pnpm dlx skills add kbarbel640-del\u002Fskills --skill equipment-telematics","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdatadrivenconstruction\u002Fequipment-telematics\u002FSKILL.md",true,[54],{"path":55,"resourceType":56,"resourceTypeDesc":57,"sizeBytes":58,"sourceHtmlUrl":59},"_meta.json","REFERENCE","参考文档",308,"https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fdatadrivenconstruction\u002Fequipment-telematics\u002F_meta.json",[61,92,120,141,147,167],{"skillNo":62,"name":63,"nameZh":6,"description":64,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":65,"categories":66,"keywords":72,"publishedAt":27},"01M6G8J55967D6281ACMKAKTEQ","crowd-prompting","A marketplace where AI agents improve prompts, system instructions, tool descriptions, and other text-based content with domain expertise from real-world operations — and earn tokens for valuable contributions.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzzadrian\u002Fcrowd-prompting\u002FSKILL.md",[67],{"categoryNo":68,"categoryName":69,"taxonomyType":70,"taxonomyTypeDesc":71},"01JBSEEDCAT000000000000036","内容创作者","PROFESSION","适用职业",[73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91],"crowd prompting","prompting","api","with","tokens","domain","world","prompts","operations","instructions","text","tool","system","improve","productivity","marketplace","https","content","ai agents",{"skillNo":93,"name":94,"nameZh":6,"description":95,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":96,"categories":97,"keywords":103,"publishedAt":27},"01M6G8J0PKPAYVET6BWE8W4C26","dilemm-ai","Compete in DilemmAI, the prisoner's dilemma AI arena at dilemm.ai. Use when an OpenClaw agent wants to create an account, design and submit strategy prompts for their bot, enter matchmaking, analyze historical matches and leaderboards, and iterate on bot strategy. Requires headless browser automation (no API available yet).","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzyori\u002Fdilemm-ai\u002FSKILL.md",[98],{"categoryNo":99,"categoryName":100,"taxonomyType":101,"taxonomyTypeDesc":102},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[104,75,105,21,106,107,108,109,110,111,112,113,114,115,116,117,118,80,119],"dilemm ai","arena","browser","openclaw agent","create","automation","browser automation","bot","openclaw","design","strategy","headless","compete","submit","matchmaking","use",{"skillNo":121,"name":122,"nameZh":6,"description":123,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":124,"categories":125,"keywords":129,"publishedAt":27},"01M6G8HWAF3K9SW7NP3Z83CPJ6","arxiv-paper-reviews","Interact with arXiv Crawler API to fetch papers, read reviews, and submit comments. Use when working with arXiv papers, fetching paper lists by date\u002Fcategory\u002Finterest, viewing paper details with comments, or submitting paper reviews via API at http:\u002F\u002F150.158.152.82:8000.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzxrys\u002Fweak-accept\u002FSKILL.md",[126],{"categoryNo":127,"categoryName":128,"taxonomyType":101,"taxonomyTypeDesc":102},"01JBSEEDCAT000000000000021","资料检索与研究",[130,131,132,133,134,135,136,76,137,117,119,138,75,139,140],"arxiv paper reviews","reviews","arxiv","paper","crawler","papers","comments","fetch","read","http","via",{"skillNo":142,"name":122,"nameZh":6,"description":123,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":143,"categories":144,"keywords":146,"publishedAt":27},"01M6G8HQJ7N7WMY6YV7D7SSJ16","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzxrys\u002Farxiv-paper-reviews\u002FSKILL.md",[145],{"categoryNo":127,"categoryName":128,"taxonomyType":101,"taxonomyTypeDesc":102},[133,132,131,130,140,135,136,76,134,117,119,137,138,75,139],{"skillNo":148,"name":149,"nameZh":6,"description":150,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":151,"categories":152,"keywords":153,"publishedAt":27},"01M6G8HM95Y64JNVFC2HN4E2BG","git-summary","Get a quick summary of the current Git repository including status, recent commits, branches, and contributors.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fzweack\u002Fgit-summary\u002FSKILL.md",[],[154,155,156,157,158,159,160,161,162,163,164,165,166],"git","summary","git summary","commits","repository","win32","status","linux","contributors","true","quick","get","branches",{"skillNo":168,"name":169,"nameZh":6,"description":170,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":171,"categories":172,"keywords":177,"publishedAt":27},"01M6G8HGEGF05CNEJCGVYFSC4R","agent-constitution","Interact with AgentConstitution governance contracts on Base Sepolia. Check compliance, read rules, log actions, query governance state.","https:\u002F\u002Fgithub.com\u002Fkbarbel640-del\u002Fskills\u002Fblob\u002Fmain\u002Fskills\u002Fztsalexey\u002Fagent-constitution\u002FSKILL.md",[173,174],{"categoryNo":99,"categoryName":100,"taxonomyType":101,"taxonomyTypeDesc":102},{"categoryNo":175,"categoryName":176,"taxonomyType":70,"taxonomyTypeDesc":71},"01JBSEEDCAT000000000000037","律师与法务",[178,179,180,181,76,182,183,33,184,138,185,186,187,188,189,89,190,191,192],"agent constitution","constitution","contracts","tree","query","base","log","check","Rules","projects","compliance","actions","state","governance","github"]