actix
spacy
s3
+
pascal
gcp
emacs
notepad++
+
+
+
intellij
axum
lisp
+
+
terraform
+
+
Ο€
hack
+
+
+
+
+
rider
βˆ‘
+
cdn
+
fiber
nvim
+
::
angular
+
+
+
sublime
+
+
nest
+
+
+
+
+
+
+
linux
+
+
|>
clion
+
grafana
junit
delphi
+
->
+
composer
+
+
+
+
+
crystal
+
android
+
lit
echo
gitlab
%
+
py
pytest
mint
+
laravel
+
+
apex
+
+
netlify
+
+
Back to Blog
Fleet Management Systems on Alpine Linux πŸš›
Alpine Linux Transportation GPS Tracking

Fleet Management Systems on Alpine Linux πŸš›

Published Jun 13, 2025

Learn how to build a fleet management system on Alpine Linux. We will track vehicles, monitor drivers, optimize routes, and manage maintenance schedules! πŸ—ΊοΈ

25 min read
0 views
Table of Contents

Fleet management is like having a command center for all your vehicles! 🎯 It helps track locations, monitor driver behavior, optimize routes, and keep vehicles running smoothly. Let’s build a comprehensive fleet management system on Alpine Linux! πŸš€

What is Fleet Management? πŸ€”

Fleet management includes:

  • Vehicle tracking - Real-time GPS location
  • Route optimization - Find the best paths
  • Driver monitoring - Safety and performance
  • Maintenance scheduling - Preventive care
  • Fuel management - Track consumption

Think of it as a smart assistant for your entire vehicle fleet! πŸš—

Installing Core Components πŸ“¦

Set up the fleet management infrastructure:

# Update package list
sudo apk update

# Install database systems
sudo apk add postgresql postgresql-client
sudo apk add redis

# Install message broker
sudo apk add rabbitmq-server

# Install web server and runtime
sudo apk add nginx
sudo apk add nodejs npm
sudo apk add python3 py3-pip python3-dev

# Install geo libraries
sudo apk add proj geos gdal
sudo apk add postgis

# Install monitoring tools
sudo apk add prometheus grafana

GPS Tracking System πŸ“

Create the vehicle tracking backend:

# Create project structure
mkdir -p ~/fleet-management/{gps,api,dashboard,analytics}
cd ~/fleet-management

# GPS data receiver
cat > gps/gps_receiver.py << 'EOF'
#!/usr/bin/env python3
import asyncio
import json
import random
import math
from datetime import datetime
import asyncpg
import aioredis
import pika

class VehicleSimulator:
    def __init__(self, vehicle_id, route_coords):
        self.vehicle_id = vehicle_id
        self.route_coords = route_coords
        self.current_index = 0
        self.speed = random.uniform(40, 60)  # km/h
        self.fuel_level = random.uniform(50, 100)  # percentage
        self.engine_temp = random.uniform(80, 95)  # celsius
        self.odometer = random.uniform(10000, 50000)  # km
        
    def get_next_position(self):
        """Simulate vehicle movement along route"""
        if self.current_index >= len(self.route_coords) - 1:
            self.current_index = 0  # Loop back to start
            
        # Get current and next point
        current = self.route_coords[self.current_index]
        next_point = self.route_coords[self.current_index + 1]
        
        # Interpolate between points
        progress = random.uniform(0.1, 0.3)
        lat = current[0] + (next_point[0] - current[0]) * progress
        lon = current[1] + (next_point[1] - current[1]) * progress
        
        # Move to next segment occasionally
        if random.random() > 0.7:
            self.current_index += 1
            
        # Update vehicle stats
        self.speed = max(0, self.speed + random.uniform(-5, 5))
        self.fuel_level = max(0, self.fuel_level - random.uniform(0.01, 0.1))
        self.engine_temp = min(120, max(70, self.engine_temp + random.uniform(-2, 2)))
        self.odometer += self.speed / 3600  # Add distance based on speed
        
        return lat, lon
        
    def get_telemetry(self):
        """Get current vehicle telemetry"""
        lat, lon = self.get_next_position()
        
        return {
            "vehicle_id": self.vehicle_id,
            "timestamp": datetime.utcnow().isoformat(),
            "location": {
                "lat": round(lat, 6),
                "lon": round(lon, 6),
                "speed": round(self.speed, 1),
                "heading": random.randint(0, 359),
                "altitude": random.randint(100, 300)
            },
            "engine": {
                "rpm": int(self.speed * 40),  # Simplified RPM calculation
                "temperature": round(self.engine_temp, 1),
                "oil_pressure": random.uniform(30, 60),
                "fuel_level": round(self.fuel_level, 1)
            },
            "vehicle": {
                "odometer": round(self.odometer, 1),
                "battery_voltage": round(random.uniform(12.5, 14.5), 1),
                "tire_pressure": [
                    random.uniform(32, 35) for _ in range(4)
                ]
            },
            "driver": {
                "id": f"DRV{random.randint(100, 999)}",
                "status": "active",
                "harsh_braking": random.random() < 0.05,
                "harsh_acceleration": random.random() < 0.05,
                "speeding": self.speed > 80
            }
        }

class GPSReceiver:
    def __init__(self):
        self.db_pool = None
        self.redis = None
        self.rabbitmq = None
        self.vehicles = {}
        
    async def init(self):
        """Initialize connections"""
        # PostgreSQL connection
        self.db_pool = await asyncpg.create_pool(
            'postgresql://fleet_user:password@localhost/fleet_db'
        )
        
        # Redis connection
        self.redis = await aioredis.create_redis_pool('redis://localhost')
        
        # RabbitMQ connection
        connection = pika.BlockingConnection(
            pika.ConnectionParameters('localhost')
        )
        self.channel = connection.channel()
        self.channel.queue_declare(queue='gps_data')
        
        # Initialize database schema
        await self.init_database()
        
    async def init_database(self):
        """Create database tables"""
        async with self.db_pool.acquire() as conn:
            await conn.execute('''
                CREATE TABLE IF NOT EXISTS vehicles (
                    id VARCHAR(50) PRIMARY KEY,
                    make VARCHAR(50),
                    model VARCHAR(50),
                    year INTEGER,
                    license_plate VARCHAR(20),
                    vin VARCHAR(50),
                    status VARCHAR(20) DEFAULT 'active'
                );
                
                CREATE TABLE IF NOT EXISTS gps_history (
                    id SERIAL PRIMARY KEY,
                    vehicle_id VARCHAR(50) REFERENCES vehicles(id),
                    timestamp TIMESTAMPTZ DEFAULT NOW(),
                    location GEOGRAPHY(POINT),
                    speed FLOAT,
                    heading INTEGER,
                    telemetry JSONB
                );
                
                CREATE INDEX idx_gps_vehicle_time ON gps_history(vehicle_id, timestamp DESC);
                
                CREATE TABLE IF NOT EXISTS trips (
                    id SERIAL PRIMARY KEY,
                    vehicle_id VARCHAR(50) REFERENCES vehicles(id),
                    driver_id VARCHAR(50),
                    start_time TIMESTAMPTZ,
                    end_time TIMESTAMPTZ,
                    start_location GEOGRAPHY(POINT),
                    end_location GEOGRAPHY(POINT),
                    distance_km FLOAT,
                    duration_minutes INTEGER,
                    fuel_used_liters FLOAT,
                    route_points JSONB
                );
                
                CREATE TABLE IF NOT EXISTS alerts (
                    id SERIAL PRIMARY KEY,
                    vehicle_id VARCHAR(50) REFERENCES vehicles(id),
                    alert_type VARCHAR(50),
                    severity VARCHAR(20),
                    message TEXT,
                    data JSONB,
                    created_at TIMESTAMPTZ DEFAULT NOW(),
                    resolved BOOLEAN DEFAULT FALSE
                );
                
                CREATE TABLE IF NOT EXISTS maintenance (
                    id SERIAL PRIMARY KEY,
                    vehicle_id VARCHAR(50) REFERENCES vehicles(id),
                    service_type VARCHAR(100),
                    scheduled_date DATE,
                    completed_date DATE,
                    mileage_at_service INTEGER,
                    cost DECIMAL(10,2),
                    notes TEXT
                );
            ''')
            
    async def process_gps_data(self, telemetry):
        """Process incoming GPS data"""
        vehicle_id = telemetry['vehicle_id']
        location = telemetry['location']
        
        # Store in database
        async with self.db_pool.acquire() as conn:
            await conn.execute('''
                INSERT INTO gps_history (vehicle_id, location, speed, heading, telemetry)
                VALUES ($1, ST_MakePoint($2, $3)::geography, $4, $5, $6)
            ''', vehicle_id, location['lon'], location['lat'], 
                location['speed'], location['heading'], json.dumps(telemetry))
        
        # Update real-time cache
        await self.redis.setex(
            f"vehicle:{vehicle_id}:location",
            60,  # TTL 60 seconds
            json.dumps(telemetry)
        )
        
        # Check for alerts
        await self.check_alerts(telemetry)
        
        # Publish to message queue
        self.channel.basic_publish(
            exchange='',
            routing_key='gps_data',
            body=json.dumps(telemetry)
        )
        
    async def check_alerts(self, telemetry):
        """Check for alert conditions"""
        vehicle_id = telemetry['vehicle_id']
        alerts = []
        
        # Speeding alert
        if telemetry['location']['speed'] > 80:
            alerts.append({
                'type': 'speeding',
                'severity': 'warning',
                'message': f"Vehicle {vehicle_id} exceeding speed limit: {telemetry['location']['speed']} km/h"
            })
            
        # Low fuel alert
        if telemetry['engine']['fuel_level'] < 20:
            alerts.append({
                'type': 'low_fuel',
                'severity': 'warning',
                'message': f"Vehicle {vehicle_id} low fuel: {telemetry['engine']['fuel_level']}%"
            })
            
        # Engine temperature alert
        if telemetry['engine']['temperature'] > 100:
            alerts.append({
                'type': 'engine_temp',
                'severity': 'critical',
                'message': f"Vehicle {vehicle_id} engine overheating: {telemetry['engine']['temperature']}Β°C"
            })
            
        # Harsh driving alerts
        if telemetry['driver']['harsh_braking'] or telemetry['driver']['harsh_acceleration']:
            alerts.append({
                'type': 'harsh_driving',
                'severity': 'info',
                'message': f"Vehicle {vehicle_id} harsh driving detected"
            })
            
        # Store alerts
        async with self.db_pool.acquire() as conn:
            for alert in alerts:
                await conn.execute('''
                    INSERT INTO alerts (vehicle_id, alert_type, severity, message, data)
                    VALUES ($1, $2, $3, $4, $5)
                ''', vehicle_id, alert['type'], alert['severity'], 
                    alert['message'], json.dumps(telemetry))
                    
    async def simulate_fleet(self):
        """Simulate a fleet of vehicles"""
        # Sample routes (latitude, longitude pairs)
        routes = [
            # Route 1: City center loop
            [(37.7749, -122.4194), (37.7849, -122.4094), (37.7949, -122.4194), 
             (37.7849, -122.4294), (37.7749, -122.4194)],
            # Route 2: Highway route
            [(37.7749, -122.4194), (37.8049, -122.4494), (37.8349, -122.4794),
             (37.8649, -122.5094), (37.8349, -122.4794), (37.7749, -122.4194)],
            # Route 3: Suburban route
            [(37.7549, -122.4394), (37.7349, -122.4594), (37.7149, -122.4794),
             (37.7349, -122.4594), (37.7549, -122.4394)]
        ]
        
        # Create vehicle simulators
        for i in range(10):
            vehicle_id = f"VEH{1000 + i}"
            route = routes[i % len(routes)]
            self.vehicles[vehicle_id] = VehicleSimulator(vehicle_id, route)
            
            # Add vehicle to database
            async with self.db_pool.acquire() as conn:
                await conn.execute('''
                    INSERT INTO vehicles (id, make, model, year, license_plate)
                    VALUES ($1, $2, $3, $4, $5)
                    ON CONFLICT (id) DO NOTHING
                ''', vehicle_id, 'Ford', 'Transit', 2022, f"ABC{1000 + i}")
                
        # Start simulation
        while True:
            tasks = []
            for vehicle in self.vehicles.values():
                telemetry = vehicle.get_telemetry()
                tasks.append(self.process_gps_data(telemetry))
                
            await asyncio.gather(*tasks)
            await asyncio.sleep(5)  # Update every 5 seconds

async def main():
    receiver = GPSReceiver()
    await receiver.init()
    await receiver.simulate_fleet()

if __name__ == "__main__":
    asyncio.run(main())
EOF

chmod +x gps/gps_receiver.py

Fleet Management API πŸ”Œ

Build the REST API for fleet operations:

# API server
cd api
npm init -y
npm install express cors body-parser pg redis socket.io amqplib
npm install bcrypt jsonwebtoken
npm install @turf/turf  # For geo calculations

cat > server.js << 'EOF'
const express = require('express');
const cors = require('cors');
const bodyParser = require('body-parser');
const { Pool } = require('pg');
const redis = require('redis');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const socketIo = require('socket.io');
const amqp = require('amqplib');
const turf = require('@turf/turf');

const app = express();
const server = require('http').createServer(app);
const io = socketIo(server, { cors: { origin: '*' } });

// Middleware
app.use(cors());
app.use(bodyParser.json());

// Database connection
const pool = new Pool({
    user: 'fleet_user',
    host: 'localhost',
    database: 'fleet_db',
    password: 'password',
    port: 5432,
});

// Redis connection
const redisClient = redis.createClient();
redisClient.connect();

// RabbitMQ connection
let channel;
amqp.connect('amqp://localhost').then(conn => {
    return conn.createChannel();
}).then(ch => {
    channel = ch;
    return channel.assertQueue('gps_data');
}).then(() => {
    // Consume GPS data and broadcast via WebSocket
    channel.consume('gps_data', (msg) => {
        if (msg) {
            const data = JSON.parse(msg.content.toString());
            io.emit('gps_update', data);
            channel.ack(msg);
        }
    });
});

// Authentication middleware
const authenticate = (req, res, next) => {
    const token = req.headers.authorization?.split(' ')[1];
    if (!token) {
        return res.status(401).json({ error: 'No token provided' });
    }
    
    try {
        const decoded = jwt.verify(token, 'your-secret-key');
        req.user = decoded;
        next();
    } catch (error) {
        res.status(401).json({ error: 'Invalid token' });
    }
};

// Routes

// Get all vehicles
app.get('/api/vehicles', authenticate, async (req, res) => {
    try {
        const result = await pool.query(`
            SELECT v.*, 
                   ST_AsGeoJSON(h.location) as last_location,
                   h.speed as last_speed,
                   h.timestamp as last_update
            FROM vehicles v
            LEFT JOIN LATERAL (
                SELECT location, speed, timestamp
                FROM gps_history
                WHERE vehicle_id = v.id
                ORDER BY timestamp DESC
                LIMIT 1
            ) h ON true
            ORDER BY v.id
        `);
        
        const vehicles = await Promise.all(result.rows.map(async (vehicle) => {
            // Get real-time data from Redis
            const realtimeData = await redisClient.get(`vehicle:${vehicle.id}:location`);
            if (realtimeData) {
                const telemetry = JSON.parse(realtimeData);
                vehicle.realtime = telemetry;
            }
            
            if (vehicle.last_location) {
                vehicle.last_location = JSON.parse(vehicle.last_location);
            }
            
            return vehicle;
        }));
        
        res.json(vehicles);
    } catch (error) {
        console.error('Error fetching vehicles:', error);
        res.status(500).json({ error: 'Failed to fetch vehicles' });
    }
});

// Get vehicle details
app.get('/api/vehicles/:id', authenticate, async (req, res) => {
    try {
        const { id } = req.params;
        
        // Get vehicle info
        const vehicleResult = await pool.query(
            'SELECT * FROM vehicles WHERE id = $1',
            [id]
        );
        
        if (vehicleResult.rows.length === 0) {
            return res.status(404).json({ error: 'Vehicle not found' });
        }
        
        const vehicle = vehicleResult.rows[0];
        
        // Get latest position
        const positionResult = await pool.query(`
            SELECT ST_AsGeoJSON(location) as location, speed, heading, timestamp, telemetry
            FROM gps_history
            WHERE vehicle_id = $1
            ORDER BY timestamp DESC
            LIMIT 1
        `, [id]);
        
        if (positionResult.rows.length > 0) {
            vehicle.current_position = {
                ...positionResult.rows[0],
                location: JSON.parse(positionResult.rows[0].location)
            };
        }
        
        // Get today's route
        const routeResult = await pool.query(`
            SELECT ST_AsGeoJSON(location) as location, timestamp, speed
            FROM gps_history
            WHERE vehicle_id = $1 AND timestamp > NOW() - INTERVAL '24 hours'
            ORDER BY timestamp
        `, [id]);
        
        vehicle.todays_route = routeResult.rows.map(point => ({
            ...point,
            location: JSON.parse(point.location)
        }));
        
        // Get active alerts
        const alertsResult = await pool.query(`
            SELECT * FROM alerts
            WHERE vehicle_id = $1 AND resolved = false
            ORDER BY created_at DESC
        `, [id]);
        
        vehicle.active_alerts = alertsResult.rows;
        
        res.json(vehicle);
    } catch (error) {
        console.error('Error fetching vehicle details:', error);
        res.status(500).json({ error: 'Failed to fetch vehicle details' });
    }
});

// Get vehicle history
app.get('/api/vehicles/:id/history', authenticate, async (req, res) => {
    try {
        const { id } = req.params;
        const { start_date, end_date } = req.query;
        
        let query = `
            SELECT ST_AsGeoJSON(location) as location, speed, heading, timestamp
            FROM gps_history
            WHERE vehicle_id = $1
        `;
        const params = [id];
        
        if (start_date) {
            query += ' AND timestamp >= $2';
            params.push(start_date);
        }
        
        if (end_date) {
            query += ` AND timestamp <= $${params.length + 1}`;
            params.push(end_date);
        }
        
        query += ' ORDER BY timestamp DESC LIMIT 1000';
        
        const result = await pool.query(query, params);
        
        const history = result.rows.map(point => ({
            ...point,
            location: JSON.parse(point.location)
        }));
        
        res.json(history);
    } catch (error) {
        console.error('Error fetching vehicle history:', error);
        res.status(500).json({ error: 'Failed to fetch vehicle history' });
    }
});

// Calculate route statistics
app.get('/api/vehicles/:id/stats', authenticate, async (req, res) => {
    try {
        const { id } = req.params;
        const { date = new Date().toISOString().split('T')[0] } = req.query;
        
        // Get all points for the day
        const result = await pool.query(`
            SELECT ST_X(location::geometry) as lon, 
                   ST_Y(location::geometry) as lat,
                   speed, timestamp, telemetry
            FROM gps_history
            WHERE vehicle_id = $1 
                  AND DATE(timestamp) = $2
            ORDER BY timestamp
        `, [id, date]);
        
        if (result.rows.length < 2) {
            return res.json({
                date,
                total_distance: 0,
                total_time: 0,
                average_speed: 0,
                max_speed: 0,
                fuel_consumed: 0,
                idle_time: 0
            });
        }
        
        // Calculate statistics
        let totalDistance = 0;
        let maxSpeed = 0;
        let idleTime = 0;
        let fuelStart = null;
        let fuelEnd = null;
        
        for (let i = 1; i < result.rows.length; i++) {
            const prev = result.rows[i - 1];
            const curr = result.rows[i];
            
            // Calculate distance
            const from = turf.point([prev.lon, prev.lat]);
            const to = turf.point([curr.lon, curr.lat]);
            const distance = turf.distance(from, to, { units: 'kilometers' });
            totalDistance += distance;
            
            // Track max speed
            maxSpeed = Math.max(maxSpeed, curr.speed);
            
            // Track idle time
            if (curr.speed < 5) {
                const timeDiff = (new Date(curr.timestamp) - new Date(prev.timestamp)) / 1000 / 60;
                idleTime += timeDiff;
            }
            
            // Track fuel
            if (i === 1 && prev.telemetry?.engine?.fuel_level) {
                fuelStart = prev.telemetry.engine.fuel_level;
            }
            if (i === result.rows.length - 1 && curr.telemetry?.engine?.fuel_level) {
                fuelEnd = curr.telemetry.engine.fuel_level;
            }
        }
        
        const firstPoint = result.rows[0];
        const lastPoint = result.rows[result.rows.length - 1];
        const totalTime = (new Date(lastPoint.timestamp) - new Date(firstPoint.timestamp)) / 1000 / 60; // minutes
        
        res.json({
            date,
            total_distance: Math.round(totalDistance * 100) / 100,
            total_time: Math.round(totalTime),
            average_speed: totalDistance / (totalTime / 60),
            max_speed: maxSpeed,
            fuel_consumed: fuelStart && fuelEnd ? fuelStart - fuelEnd : 0,
            idle_time: Math.round(idleTime),
            stops: result.rows.filter(p => p.speed < 1).length
        });
    } catch (error) {
        console.error('Error calculating stats:', error);
        res.status(500).json({ error: 'Failed to calculate statistics' });
    }
});

// Get alerts
app.get('/api/alerts', authenticate, async (req, res) => {
    try {
        const { vehicle_id, severity, resolved } = req.query;
        
        let query = 'SELECT * FROM alerts WHERE 1=1';
        const params = [];
        
        if (vehicle_id) {
            params.push(vehicle_id);
            query += ` AND vehicle_id = $${params.length}`;
        }
        
        if (severity) {
            params.push(severity);
            query += ` AND severity = $${params.length}`;
        }
        
        if (resolved !== undefined) {
            params.push(resolved === 'true');
            query += ` AND resolved = $${params.length}`;
        }
        
        query += ' ORDER BY created_at DESC LIMIT 100';
        
        const result = await pool.query(query, params);
        res.json(result.rows);
    } catch (error) {
        console.error('Error fetching alerts:', error);
        res.status(500).json({ error: 'Failed to fetch alerts' });
    }
});

// Resolve alert
app.put('/api/alerts/:id/resolve', authenticate, async (req, res) => {
    try {
        const { id } = req.params;
        
        await pool.query(
            'UPDATE alerts SET resolved = true WHERE id = $1',
            [id]
        );
        
        res.json({ success: true });
    } catch (error) {
        console.error('Error resolving alert:', error);
        res.status(500).json({ error: 'Failed to resolve alert' });
    }
});

// Get trips
app.get('/api/trips', authenticate, async (req, res) => {
    try {
        const { vehicle_id, driver_id, date } = req.query;
        
        let query = `
            SELECT t.*, 
                   ST_AsGeoJSON(t.start_location) as start_location,
                   ST_AsGeoJSON(t.end_location) as end_location,
                   v.make, v.model, v.license_plate
            FROM trips t
            JOIN vehicles v ON t.vehicle_id = v.id
            WHERE 1=1
        `;
        const params = [];
        
        if (vehicle_id) {
            params.push(vehicle_id);
            query += ` AND t.vehicle_id = $${params.length}`;
        }
        
        if (driver_id) {
            params.push(driver_id);
            query += ` AND t.driver_id = $${params.length}`;
        }
        
        if (date) {
            params.push(date);
            query += ` AND DATE(t.start_time) = $${params.length}`;
        }
        
        query += ' ORDER BY t.start_time DESC LIMIT 100';
        
        const result = await pool.query(query, params);
        
        const trips = result.rows.map(trip => ({
            ...trip,
            start_location: trip.start_location ? JSON.parse(trip.start_location) : null,
            end_location: trip.end_location ? JSON.parse(trip.end_location) : null
        }));
        
        res.json(trips);
    } catch (error) {
        console.error('Error fetching trips:', error);
        res.status(500).json({ error: 'Failed to fetch trips' });
    }
});

// Schedule maintenance
app.post('/api/maintenance', authenticate, async (req, res) => {
    try {
        const { vehicle_id, service_type, scheduled_date, notes } = req.body;
        
        const result = await pool.query(`
            INSERT INTO maintenance (vehicle_id, service_type, scheduled_date, notes)
            VALUES ($1, $2, $3, $4)
            RETURNING *
        `, [vehicle_id, service_type, scheduled_date, notes]);
        
        res.json(result.rows[0]);
    } catch (error) {
        console.error('Error scheduling maintenance:', error);
        res.status(500).json({ error: 'Failed to schedule maintenance' });
    }
});

// Get maintenance schedule
app.get('/api/maintenance', authenticate, async (req, res) => {
    try {
        const { vehicle_id, upcoming } = req.query;
        
        let query = `
            SELECT m.*, v.make, v.model, v.license_plate
            FROM maintenance m
            JOIN vehicles v ON m.vehicle_id = v.id
            WHERE 1=1
        `;
        const params = [];
        
        if (vehicle_id) {
            params.push(vehicle_id);
            query += ` AND m.vehicle_id = $${params.length}`;
        }
        
        if (upcoming === 'true') {
            query += ' AND m.completed_date IS NULL AND m.scheduled_date >= CURRENT_DATE';
        }
        
        query += ' ORDER BY m.scheduled_date';
        
        const result = await pool.query(query, params);
        res.json(result.rows);
    } catch (error) {
        console.error('Error fetching maintenance:', error);
        res.status(500).json({ error: 'Failed to fetch maintenance' });
    }
});

// WebSocket connection for real-time updates
io.on('connection', (socket) => {
    console.log('Client connected:', socket.id);
    
    // Join vehicle-specific rooms
    socket.on('track_vehicle', (vehicleId) => {
        socket.join(`vehicle:${vehicleId}`);
    });
    
    socket.on('untrack_vehicle', (vehicleId) => {
        socket.leave(`vehicle:${vehicleId}`);
    });
    
    socket.on('disconnect', () => {
        console.log('Client disconnected:', socket.id);
    });
});

// Start server
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
    console.log(`Fleet Management API running on port ${PORT}`);
});
EOF

Fleet Dashboard Interface πŸ–₯️

Create the web dashboard:

# Dashboard frontend
cd ../dashboard
cat > index.html << 'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Fleet Management Dashboard</title>
    <link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/leaflet.css" />
    <script src="https://unpkg.com/[email protected]/dist/leaflet.js"></script>
    <script src="/socket.io/socket.io.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            background: #f5f5f5;
        }
        .header {
            background: #2c3e50;
            color: white;
            padding: 1rem 2rem;
            display: flex;
            justify-content: space-between;
            align-items: center;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        .container {
            display: grid;
            grid-template-columns: 300px 1fr;
            height: calc(100vh - 60px);
        }
        .sidebar {
            background: white;
            border-right: 1px solid #e0e0e0;
            overflow-y: auto;
        }
        .main {
            position: relative;
            overflow: hidden;
        }
        #map {
            width: 100%;
            height: 100%;
        }
        .vehicle-list {
            padding: 1rem;
        }
        .vehicle-item {
            background: #f8f8f8;
            padding: 1rem;
            margin-bottom: 0.5rem;
            border-radius: 8px;
            cursor: pointer;
            transition: all 0.2s;
            border: 2px solid transparent;
        }
        .vehicle-item:hover {
            background: #e8e8e8;
        }
        .vehicle-item.selected {
            border-color: #3498db;
            background: #e3f2fd;
        }
        .vehicle-status {
            display: inline-block;
            width: 10px;
            height: 10px;
            border-radius: 50%;
            margin-right: 0.5rem;
        }
        .status-active { background: #27ae60; }
        .status-idle { background: #f39c12; }
        .status-offline { background: #e74c3c; }
        .vehicle-info {
            margin-top: 0.5rem;
            font-size: 0.85rem;
            color: #666;
        }
        .stats-panel {
            position: absolute;
            top: 1rem;
            right: 1rem;
            background: white;
            padding: 1.5rem;
            border-radius: 8px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
            width: 300px;
            max-height: 80vh;
            overflow-y: auto;
        }
        .stat-item {
            margin-bottom: 1rem;
            padding-bottom: 1rem;
            border-bottom: 1px solid #eee;
        }
        .stat-label {
            font-size: 0.85rem;
            color: #666;
            margin-bottom: 0.25rem;
        }
        .stat-value {
            font-size: 1.5rem;
            font-weight: bold;
            color: #2c3e50;
        }
        .alert-badge {
            background: #e74c3c;
            color: white;
            padding: 0.25rem 0.5rem;
            border-radius: 12px;
            font-size: 0.75rem;
            margin-left: 0.5rem;
        }
        .vehicle-marker {
            background: #3498db;
            color: white;
            padding: 0.5rem;
            border-radius: 50%;
            text-align: center;
            font-weight: bold;
            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
        }
        .route-info {
            background: #f8f8f8;
            padding: 1rem;
            border-radius: 8px;
            margin-top: 1rem;
        }
        .tabs {
            display: flex;
            gap: 1rem;
            margin-bottom: 1rem;
            border-bottom: 1px solid #e0e0e0;
        }
        .tab {
            padding: 0.5rem 1rem;
            cursor: pointer;
            border-bottom: 2px solid transparent;
        }
        .tab.active {
            color: #3498db;
            border-bottom-color: #3498db;
        }
        .tab-content {
            display: none;
        }
        .tab-content.active {
            display: block;
        }
        #alerts-list {
            max-height: 300px;
            overflow-y: auto;
        }
        .alert-item {
            background: #fff3cd;
            border: 1px solid #ffeaa7;
            padding: 0.75rem;
            margin-bottom: 0.5rem;
            border-radius: 4px;
            font-size: 0.85rem;
        }
        .alert-item.critical {
            background: #f8d7da;
            border-color: #f5c6cb;
        }
        .btn {
            background: #3498db;
            color: white;
            border: none;
            padding: 0.5rem 1rem;
            border-radius: 4px;
            cursor: pointer;
            font-size: 0.9rem;
        }
        .btn:hover {
            background: #2980b9;
        }
        .btn-small {
            padding: 0.25rem 0.5rem;
            font-size: 0.8rem;
        }
    </style>
</head>
<body>
    <header class="header">
        <h1>πŸš› Fleet Management System</h1>
        <div>
            <span id="total-vehicles">0</span> Vehicles | 
            <span id="active-vehicles">0</span> Active | 
            <span id="total-alerts">0</span> Alerts
        </div>
    </header>
    
    <div class="container">
        <aside class="sidebar">
            <div class="tabs">
                <div class="tab active" onclick="switchTab('vehicles')">Vehicles</div>
                <div class="tab" onclick="switchTab('alerts')">Alerts</div>
            </div>
            
            <div id="vehicles-tab" class="tab-content active">
                <div class="vehicle-list" id="vehicle-list">
                    <!-- Vehicles will be loaded here -->
                </div>
            </div>
            
            <div id="alerts-tab" class="tab-content">
                <div id="alerts-list">
                    <!-- Alerts will be loaded here -->
                </div>
            </div>
        </aside>
        
        <main class="main">
            <div id="map"></div>
            
            <div class="stats-panel" id="stats-panel" style="display: none;">
                <h3 id="selected-vehicle">No Vehicle Selected</h3>
                
                <div class="stat-item">
                    <div class="stat-label">Current Speed</div>
                    <div class="stat-value" id="current-speed">0 km/h</div>
                </div>
                
                <div class="stat-item">
                    <div class="stat-label">Location</div>
                    <div id="current-location">Unknown</div>
                </div>
                
                <div class="stat-item">
                    <div class="stat-label">Driver</div>
                    <div id="current-driver">Unknown</div>
                </div>
                
                <div class="stat-item">
                    <div class="stat-label">Fuel Level</div>
                    <div class="stat-value" id="fuel-level">0%</div>
                </div>
                
                <div class="stat-item">
                    <div class="stat-label">Today's Distance</div>
                    <div class="stat-value" id="todays-distance">0 km</div>
                </div>
                
                <div class="route-info">
                    <h4>Today's Route</h4>
                    <canvas id="speed-chart" width="300" height="150"></canvas>
                </div>
                
                <button class="btn" onclick="showVehicleHistory()">View History</button>
                <button class="btn" onclick="scheduleMainenance()">Schedule Maintenance</button>
            </div>
        </main>
    </div>
    
    <script>
        // Initialize map
        const map = L.map('map').setView([37.7749, -122.4194], 12);
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            attribution: 'Β© OpenStreetMap contributors'
        }).addTo(map);
        
        // WebSocket connection
        const socket = io('http://localhost:3000');
        
        // Data storage
        let vehicles = {};
        let vehicleMarkers = {};
        let selectedVehicle = null;
        let routePolylines = {};
        let speedChart = null;
        
        // Custom vehicle icon
        const vehicleIcon = L.divIcon({
            className: 'vehicle-marker',
            html: '<div>πŸš›</div>',
            iconSize: [30, 30]
        });
        
        // Initialize
        async function init() {
            await loadVehicles();
            await loadAlerts();
            setupSocketListeners();
            initSpeedChart();
        }
        
        // Load vehicles
        async function loadVehicles() {
            const response = await fetch('/api/vehicles', {
                headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }
            });
            
            const vehicleList = await response.json();
            
            document.getElementById('total-vehicles').textContent = vehicleList.length;
            
            const vehicleListEl = document.getElementById('vehicle-list');
            vehicleListEl.innerHTML = '';
            
            let activeCount = 0;
            
            vehicleList.forEach(vehicle => {
                vehicles[vehicle.id] = vehicle;
                
                // Add to sidebar
                const item = document.createElement('div');
                item.className = 'vehicle-item';
                item.onclick = () => selectVehicle(vehicle.id);
                
                const isActive = vehicle.realtime && 
                    (Date.now() - new Date(vehicle.realtime.timestamp).getTime()) < 60000;
                
                if (isActive) activeCount++;
                
                item.innerHTML = `
                    <div>
                        <span class="vehicle-status ${isActive ? 'status-active' : 'status-offline'}"></span>
                        <strong>${vehicle.id}</strong>
                        ${vehicle.realtime?.driver?.speeding ? '<span class="alert-badge">⚑</span>' : ''}
                    </div>
                    <div class="vehicle-info">
                        ${vehicle.make} ${vehicle.model} - ${vehicle.license_plate}<br>
                        ${vehicle.realtime ? 
                            `${vehicle.realtime.location.speed.toFixed(1)} km/h` : 
                            'Offline'}
                    </div>
                `;
                
                vehicleListEl.appendChild(item);
                
                // Add to map
                if (vehicle.realtime) {
                    addVehicleToMap(vehicle);
                }
            });
            
            document.getElementById('active-vehicles').textContent = activeCount;
        }
        
        // Add vehicle to map
        function addVehicleToMap(vehicle) {
            const location = vehicle.realtime.location;
            
            if (vehicleMarkers[vehicle.id]) {
                vehicleMarkers[vehicle.id].setLatLng([location.lat, location.lon]);
            } else {
                const marker = L.marker([location.lat, location.lon], { icon: vehicleIcon })
                    .bindPopup(`
                        <strong>${vehicle.id}</strong><br>
                        ${vehicle.make} ${vehicle.model}<br>
                        Speed: ${location.speed.toFixed(1)} km/h
                    `)
                    .addTo(map);
                
                vehicleMarkers[vehicle.id] = marker;
            }
        }
        
        // Select vehicle
        async function selectVehicle(vehicleId) {
            selectedVehicle = vehicleId;
            
            // Update UI
            document.querySelectorAll('.vehicle-item').forEach(item => {
                item.classList.remove('selected');
            });
            event.currentTarget.classList.add('selected');
            
            // Show stats panel
            document.getElementById('stats-panel').style.display = 'block';
            document.getElementById('selected-vehicle').textContent = vehicleId;
            
            // Load vehicle details
            const response = await fetch(`/api/vehicles/${vehicleId}`, {
                headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }
            });
            
            const vehicle = await response.json();
            
            // Update stats
            if (vehicle.current_position) {
                const telemetry = vehicle.current_position.telemetry;
                document.getElementById('current-speed').textContent = 
                    `${telemetry.location.speed.toFixed(1)} km/h`;
                document.getElementById('current-location').textContent = 
                    `${telemetry.location.lat.toFixed(4)}, ${telemetry.location.lon.toFixed(4)}`;
                document.getElementById('current-driver').textContent = 
                    telemetry.driver.id;
                document.getElementById('fuel-level').textContent = 
                    `${telemetry.engine.fuel_level.toFixed(1)}%`;
            }
            
            // Load today's stats
            const statsResponse = await fetch(`/api/vehicles/${vehicleId}/stats`, {
                headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }
            });
            
            const stats = await statsResponse.json();
            document.getElementById('todays-distance').textContent = 
                `${stats.total_distance.toFixed(1)} km`;
            
            // Draw route
            if (vehicle.todays_route && vehicle.todays_route.length > 1) {
                drawRoute(vehicleId, vehicle.todays_route);
                updateSpeedChart(vehicle.todays_route);
            }
            
            // Center map on vehicle
            if (vehicleMarkers[vehicleId]) {
                map.setView(vehicleMarkers[vehicleId].getLatLng(), 15);
            }
            
            // Subscribe to real-time updates
            socket.emit('track_vehicle', vehicleId);
        }
        
        // Draw route on map
        function drawRoute(vehicleId, route) {
            // Remove existing route
            if (routePolylines[vehicleId]) {
                map.removeLayer(routePolylines[vehicleId]);
            }
            
            const coordinates = route.map(point => [
                point.location.coordinates[1],
                point.location.coordinates[0]
            ]);
            
            const polyline = L.polyline(coordinates, {
                color: '#3498db',
                weight: 4,
                opacity: 0.7
            }).addTo(map);
            
            routePolylines[vehicleId] = polyline;
        }
        
        // Initialize speed chart
        function initSpeedChart() {
            const ctx = document.getElementById('speed-chart').getContext('2d');
            speedChart = new Chart(ctx, {
                type: 'line',
                data: {
                    labels: [],
                    datasets: [{
                        label: 'Speed (km/h)',
                        data: [],
                        borderColor: '#3498db',
                        tension: 0.1
                    }]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    scales: {
                        y: {
                            beginAtZero: true,
                            max: 120
                        }
                    }
                }
            });
        }
        
        // Update speed chart
        function updateSpeedChart(route) {
            const labels = route.map(point => 
                new Date(point.timestamp).toLocaleTimeString()
            );
            const speeds = route.map(point => point.speed);
            
            speedChart.data.labels = labels;
            speedChart.data.datasets[0].data = speeds;
            speedChart.update();
        }
        
        // Load alerts
        async function loadAlerts() {
            const response = await fetch('/api/alerts?resolved=false', {
                headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }
            });
            
            const alerts = await response.json();
            
            document.getElementById('total-alerts').textContent = alerts.length;
            
            const alertsList = document.getElementById('alerts-list');
            alertsList.innerHTML = '';
            
            alerts.forEach(alert => {
                const item = document.createElement('div');
                item.className = `alert-item ${alert.severity}`;
                item.innerHTML = `
                    <strong>${alert.vehicle_id}</strong> - ${alert.alert_type}<br>
                    ${alert.message}<br>
                    <small>${new Date(alert.created_at).toLocaleString()}</small>
                    <button class="btn btn-small" onclick="resolveAlert(${alert.id})">Resolve</button>
                `;
                alertsList.appendChild(item);
            });
        }
        
        // Resolve alert
        async function resolveAlert(alertId) {
            await fetch(`/api/alerts/${alertId}/resolve`, {
                method: 'PUT',
                headers: { 'Authorization': 'Bearer ' + localStorage.getItem('token') }
            });
            
            loadAlerts();
        }
        
        // Setup socket listeners
        function setupSocketListeners() {
            socket.on('gps_update', (data) => {
                // Update vehicle data
                if (vehicles[data.vehicle_id]) {
                    vehicles[data.vehicle_id].realtime = data;
                    addVehicleToMap(vehicles[data.vehicle_id]);
                    
                    // Update sidebar
                    loadVehicles();
                    
                    // Update selected vehicle stats
                    if (selectedVehicle === data.vehicle_id) {
                        document.getElementById('current-speed').textContent = 
                            `${data.location.speed.toFixed(1)} km/h`;
                        document.getElementById('fuel-level').textContent = 
                            `${data.engine.fuel_level.toFixed(1)}%`;
                    }
                }
            });
        }
        
        // Switch tabs
        function switchTab(tab) {
            document.querySelectorAll('.tab').forEach(t => {
                t.classList.remove('active');
            });
            document.querySelectorAll('.tab-content').forEach(c => {
                c.classList.remove('active');
            });
            
            event.target.classList.add('active');
            document.getElementById(`${tab}-tab`).classList.add('active');
        }
        
        // Show vehicle history
        function showVehicleHistory() {
            // Implement history view
            alert('Vehicle history view coming soon!');
        }
        
        // Schedule maintenance
        function scheduleMainenance() {
            // Implement maintenance scheduling
            alert('Maintenance scheduling coming soon!');
        }
        
        // Set demo token (in production, implement proper login)
        localStorage.setItem('token', 'demo-token');
        
        // Initialize app
        init();
    </script>
</body>
</html>
EOF

Route Optimization Engine πŸ—ΊοΈ

Optimize delivery routes:

# Route optimization
cat > analytics/route_optimizer.py << 'EOF'
#!/usr/bin/env python3
import numpy as np
from scipy.spatial.distance import cdist
from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp
import asyncpg
import json

class RouteOptimizer:
    def __init__(self, db_config):
        self.db_config = db_config
        
    async def get_delivery_points(self, date):
        """Get delivery points for a specific date"""
        conn = await asyncpg.connect(**self.db_config)
        
        points = await conn.fetch('''
            SELECT id, customer_name, 
                   ST_X(location::geometry) as lon,
                   ST_Y(location::geometry) as lat,
                   delivery_window_start,
                   delivery_window_end,
                   package_weight,
                   priority
            FROM deliveries
            WHERE delivery_date = $1
            ORDER BY priority DESC
        ''', date)
        
        await conn.close()
        return points
        
    def calculate_distance_matrix(self, locations):
        """Calculate distance matrix between all points"""
        coords = np.array([[loc['lat'], loc['lon']] for loc in locations])
        
        # Calculate Euclidean distances (in production, use real road distances)
        distances = cdist(coords, coords) * 111  # Convert to km (approximation)
        
        return distances.astype(int)
        
    def optimize_routes(self, locations, num_vehicles, vehicle_capacity):
        """Optimize routes using OR-Tools"""
        # Create routing model
        manager = pywrapcp.RoutingIndexManager(
            len(locations), num_vehicles, 0
        )
        routing = pywrapcp.RoutingModel(manager)
        
        # Distance callback
        distance_matrix = self.calculate_distance_matrix(locations)
        
        def distance_callback(from_index, to_index):
            from_node = manager.IndexToNode(from_index)
            to_node = manager.IndexToNode(to_index)
            return distance_matrix[from_node][to_node]
        
        transit_callback_index = routing.RegisterTransitCallback(distance_callback)
        routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
        
        # Capacity constraint
        def demand_callback(from_index):
            from_node = manager.IndexToNode(from_index)
            return locations[from_node].get('package_weight', 0)
        
        demand_callback_index = routing.RegisterUnaryTransitCallback(demand_callback)
        routing.AddDimensionWithVehicleCapacity(
            demand_callback_index,
            0,  # null capacity slack
            [vehicle_capacity] * num_vehicles,
            True,  # start cumul to zero
            'Capacity'
        )
        
        # Time windows
        time_dimension_name = 'Time'
        routing.AddDimension(
            transit_callback_index,
            30,  # allow waiting time
            7200,  # maximum time per vehicle
            False,  # Don't force start cumul to zero
            time_dimension_name
        )
        
        time_dimension = routing.GetDimensionOrDie(time_dimension_name)
        
        # Add time window constraints
        for location_idx, location in enumerate(locations):
            if location_idx == 0:  # Skip depot
                continue
            index = manager.NodeToIndex(location_idx)
            time_dimension.CumulVar(index).SetRange(
                int(location.get('delivery_window_start', 0)),
                int(location.get('delivery_window_end', 7200))
            )
        
        # Set search parameters
        search_parameters = pywrapcp.DefaultRoutingSearchParameters()
        search_parameters.first_solution_strategy = (
            routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC
        )
        search_parameters.local_search_metaheuristic = (
            routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH
        )
        search_parameters.time_limit.FromSeconds(30)
        
        # Solve
        solution = routing.SolveWithParameters(search_parameters)
        
        if solution:
            return self.extract_routes(manager, routing, solution, locations)
        
        return None
        
    def extract_routes(self, manager, routing, solution, locations):
        """Extract routes from solution"""
        routes = []
        
        for vehicle_id in range(routing.vehicles()):
            route = []
            index = routing.Start(vehicle_id)
            
            while not routing.IsEnd(index):
                node_index = manager.IndexToNode(index)
                route.append({
                    'location': locations[node_index],
                    'arrival_time': solution.Min(
                        routing.GetDimensionOrDie('Time').CumulVar(index)
                    )
                })
                index = solution.Value(routing.NextVar(index))
            
            if len(route) > 1:  # Exclude empty routes
                routes.append({
                    'vehicle_id': vehicle_id,
                    'route': route,
                    'total_distance': routing.GetArcCostForVehicle(
                        routing.Start(vehicle_id),
                        routing.End(vehicle_id),
                        vehicle_id
                    )
                })
        
        return routes
        
    def generate_turn_by_turn_directions(self, route):
        """Generate navigation instructions"""
        directions = []
        
        for i in range(len(route) - 1):
            current = route[i]['location']
            next_loc = route[i + 1]['location']
            
            # Calculate bearing
            lat1, lon1 = current['lat'], current['lon']
            lat2, lon2 = next_loc['lat'], next_loc['lon']
            
            bearing = self.calculate_bearing(lat1, lon1, lat2, lon2)
            distance = self.calculate_distance(lat1, lon1, lat2, lon2)
            
            # Generate instruction
            turn = self.get_turn_instruction(bearing)
            
            directions.append({
                'instruction': f"{turn} and continue for {distance:.1f} km",
                'distance': distance,
                'destination': next_loc['customer_name']
            })
        
        return directions
        
    def calculate_bearing(self, lat1, lon1, lat2, lon2):
        """Calculate bearing between two points"""
        lat1, lon1, lat2, lon2 = map(np.radians, [lat1, lon1, lat2, lon2])
        
        dlon = lon2 - lon1
        
        x = np.sin(dlon) * np.cos(lat2)
        y = np.cos(lat1) * np.sin(lat2) - np.sin(lat1) * np.cos(lat2) * np.cos(dlon)
        
        bearing = np.arctan2(x, y)
        bearing = np.degrees(bearing)
        bearing = (bearing + 360) % 360
        
        return bearing
        
    def get_turn_instruction(self, bearing):
        """Convert bearing to turn instruction"""
        directions = [
            (0, 45, "Head north"),
            (45, 135, "Turn right"),
            (135, 225, "Turn around"),
            (225, 315, "Turn left"),
            (315, 360, "Head north")
        ]
        
        for start, end, instruction in directions:
            if start <= bearing < end:
                return instruction
        
        return "Continue straight"
        
    def calculate_distance(self, lat1, lon1, lat2, lon2):
        """Calculate distance between two points (Haversine formula)"""
        R = 6371  # Earth's radius in km
        
        lat1, lon1, lat2, lon2 = map(np.radians, [lat1, lon1, lat2, lon2])
        
        dlat = lat2 - lat1
        dlon = lon2 - lon1
        
        a = np.sin(dlat/2)**2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon/2)**2
        c = 2 * np.arcsin(np.sqrt(a))
        
        return R * c

# Example usage
async def main():
    optimizer = RouteOptimizer({
        'host': 'localhost',
        'database': 'fleet_db',
        'user': 'fleet_user',
        'password': 'password'
    })
    
    # Get delivery points
    locations = await optimizer.get_delivery_points('2024-01-15')
    
    # Add depot as first location
    depot = {
        'id': 0,
        'customer_name': 'Depot',
        'lat': 37.7749,
        'lon': -122.4194,
        'package_weight': 0
    }
    locations = [depot] + list(locations)
    
    # Optimize routes
    routes = optimizer.optimize_routes(
        locations,
        num_vehicles=5,
        vehicle_capacity=1000
    )
    
    # Display results
    for route in routes:
        print(f"\nVehicle {route['vehicle_id']}:")
        print(f"Total distance: {route['total_distance']} km")
        
        for stop in route['route']:
            print(f"  - {stop['location']['customer_name']} "
                  f"(Arrival: {stop['arrival_time']} min)")

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())
EOF

Maintenance Scheduler πŸ”§

Automate vehicle maintenance:

# Maintenance scheduler
cat > analytics/maintenance_scheduler.py << 'EOF'
#!/usr/bin/env python3
import asyncio
import asyncpg
from datetime import datetime, timedelta
import smtplib
from email.mime.text import MIMEText

class MaintenanceScheduler:
    def __init__(self, db_config):
        self.db_config = db_config
        self.maintenance_intervals = {
            'oil_change': {'mileage': 5000, 'days': 90},
            'tire_rotation': {'mileage': 10000, 'days': 180},
            'brake_inspection': {'mileage': 20000, 'days': 365},
            'transmission_service': {'mileage': 50000, 'days': 730},
            'annual_inspection': {'mileage': None, 'days': 365}
        }
        
    async def check_maintenance_due(self):
        """Check which vehicles need maintenance"""
        conn = await asyncpg.connect(**self.db_config)
        
        # Get all vehicles with their current mileage and last service dates
        vehicles = await conn.fetch('''
            SELECT v.id, v.make, v.model, v.license_plate,
                   h.odometer as current_mileage,
                   m.service_type,
                   m.mileage_at_service,
                   m.completed_date
            FROM vehicles v
            LEFT JOIN LATERAL (
                SELECT telemetry->>'vehicle'->>'odometer' as odometer
                FROM gps_history
                WHERE vehicle_id = v.id
                ORDER BY timestamp DESC
                LIMIT 1
            ) h ON true
            LEFT JOIN maintenance m ON v.id = m.vehicle_id
            WHERE v.status = 'active'
        ''')
        
        maintenance_due = []
        
        for vehicle in vehicles:
            for service_type, intervals in self.maintenance_intervals.items():
                if self.is_service_due(vehicle, service_type, intervals):
                    maintenance_due.append({
                        'vehicle_id': vehicle['id'],
                        'vehicle': f"{vehicle['make']} {vehicle['model']} ({vehicle['license_plate']})",
                        'service_type': service_type,
                        'current_mileage': vehicle['current_mileage'],
                        'reason': self.get_service_reason(vehicle, service_type, intervals)
                    })
        
        await conn.close()
        return maintenance_due
        
    def is_service_due(self, vehicle, service_type, intervals):
        """Check if specific service is due"""
        # Check mileage-based maintenance
        if intervals['mileage'] and vehicle['current_mileage']:
            last_service_mileage = 0
            for service in vehicle:
                if service.get('service_type') == service_type and service.get('mileage_at_service'):
                    last_service_mileage = max(last_service_mileage, service['mileage_at_service'])
            
            if vehicle['current_mileage'] - last_service_mileage >= intervals['mileage']:
                return True
        
        # Check time-based maintenance
        last_service_date = None
        for service in vehicle:
            if service.get('service_type') == service_type and service.get('completed_date'):
                if not last_service_date or service['completed_date'] > last_service_date:
                    last_service_date = service['completed_date']
        
        if last_service_date:
            days_since_service = (datetime.now().date() - last_service_date).days
            if days_since_service >= intervals['days']:
                return True
        elif intervals['days']:  # No previous service record
            return True
        
        return False
        
    def get_service_reason(self, vehicle, service_type, intervals):
        """Get reason why service is due"""
        reasons = []
        
        if intervals['mileage']:
            reasons.append(f"Every {intervals['mileage']:,} km")
        
        if intervals['days']:
            reasons.append(f"Every {intervals['days']} days")
        
        return " or ".join(reasons)
        
    async def schedule_maintenance(self, vehicle_id, service_type, scheduled_date):
        """Schedule maintenance for a vehicle"""
        conn = await asyncpg.connect(**self.db_config)
        
        await conn.execute('''
            INSERT INTO maintenance (vehicle_id, service_type, scheduled_date)
            VALUES ($1, $2, $3)
        ''', vehicle_id, service_type, scheduled_date)
        
        await conn.close()
        
    async def send_maintenance_alerts(self, maintenance_due):
        """Send email alerts for due maintenance"""
        if not maintenance_due:
            return
        
        # Group by service type
        by_service = {}
        for item in maintenance_due:
            service = item['service_type']
            if service not in by_service:
                by_service[service] = []
            by_service[service].append(item)
        
        # Create email content
        html_content = """
        <html>
        <body>
            <h2>Fleet Maintenance Alert</h2>
            <p>The following vehicles require maintenance:</p>
        """
        
        for service_type, vehicles in by_service.items():
            html_content += f"<h3>{service_type.replace('_', ' ').title()}</h3><ul>"
            for vehicle in vehicles:
                html_content += f"<li>{vehicle['vehicle']} - {vehicle['reason']}</li>"
            html_content += "</ul>"
        
        html_content += """
            <p>Please schedule maintenance appointments as soon as possible.</p>
        </body>
        </html>
        """
        
        # Send email (configure SMTP settings)
        msg = MIMEText(html_content, 'html')
        msg['Subject'] = f'Fleet Maintenance Alert - {len(maintenance_due)} vehicles need service'
        msg['From'] = '[email protected]'
        msg['To'] = '[email protected]'
        
        # In production, configure proper SMTP
        # smtp = smtplib.SMTP('localhost')
        # smtp.send_message(msg)
        # smtp.quit()
        
        print(f"Maintenance alert sent for {len(maintenance_due)} vehicles")
        
    async def run_daily_check(self):
        """Run daily maintenance check"""
        print(f"Running maintenance check at {datetime.now()}")
        
        maintenance_due = await self.check_maintenance_due()
        
        if maintenance_due:
            print(f"Found {len(maintenance_due)} vehicles needing maintenance:")
            for item in maintenance_due:
                print(f"  - {item['vehicle']}: {item['service_type']}")
            
            await self.send_maintenance_alerts(maintenance_due)
            
            # Auto-schedule maintenance for next week
            next_week = datetime.now().date() + timedelta(days=7)
            for item in maintenance_due:
                await self.schedule_maintenance(
                    item['vehicle_id'],
                    item['service_type'],
                    next_week
                )
        else:
            print("No vehicles require maintenance at this time")

# Run scheduler
async def main():
    scheduler = MaintenanceScheduler({
        'host': 'localhost',
        'database': 'fleet_db',
        'user': 'fleet_user',
        'password': 'password'
    })
    
    # Run once
    await scheduler.run_daily_check()
    
    # Or run continuously
    # while True:
    #     await scheduler.run_daily_check()
    #     await asyncio.sleep(86400)  # Check daily

if __name__ == "__main__":
    asyncio.run(main())
EOF

System Configuration πŸ”§

Set up the complete system:

# Database setup
cat > setup_database.sql << 'EOF'
-- Create database and user
CREATE DATABASE fleet_db;
CREATE USER fleet_user WITH PASSWORD 'password';
GRANT ALL PRIVILEGES ON DATABASE fleet_db TO fleet_user;

-- Connect to fleet_db
\c fleet_db

-- Enable PostGIS extension
CREATE EXTENSION IF NOT EXISTS postgis;

-- Grant permissions
GRANT ALL ON ALL TABLES IN SCHEMA public TO fleet_user;
GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO fleet_user;
EOF

# System startup script
cat > start_fleet_system.sh << 'EOF'
#!/bin/sh
# Start Fleet Management System

echo "Starting Fleet Management System..."

# Start PostgreSQL
sudo rc-service postgresql start

# Start Redis
redis-server --daemonize yes

# Start RabbitMQ
sudo rc-service rabbitmq-server start

# Start GPS receiver (simulator)
cd ~/fleet-management
python3 gps/gps_receiver.py &

# Start API server
cd api && npm start &

# Start maintenance scheduler
python3 analytics/maintenance_scheduler.py &

# Serve dashboard
cd ../dashboard
python3 -m http.server 8080 &

echo "Fleet Management System started!"
echo "Dashboard: http://localhost:8080"
echo "API: http://localhost:3000"
EOF

chmod +x start_fleet_system.sh

Best Practices πŸ“Œ

  1. Data retention - Archive old GPS data
  2. Real-time accuracy - Use quality GPS hardware
  3. Driver privacy - Implement data protection
  4. Scalability - Design for fleet growth
  5. Redundancy - Backup critical systems

Troubleshooting πŸ”§

GPS Data Not Updating

# Check GPS receiver
ps aux | grep gps_receiver

# Check MQTT messages
mosquitto_sub -t "gps/#" -v

# Check database connection
psql -U fleet_user -d fleet_db -c "SELECT COUNT(*) FROM gps_history"

Performance Issues

# Optimize PostGIS queries
CREATE INDEX idx_gps_history_geography ON gps_history USING GIST(location);

# Partition large tables
CREATE TABLE gps_history_2024_01 PARTITION OF gps_history
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');

Quick Commands πŸ“‹

# Check vehicle locations
curl http://localhost:3000/api/vehicles -H "Authorization: Bearer token"

# Get vehicle route
curl http://localhost:3000/api/vehicles/VEH1001/history?date=2024-01-15

# Schedule maintenance
curl -X POST http://localhost:3000/api/maintenance \
  -H "Content-Type: application/json" \
  -d '{"vehicle_id":"VEH1001","service_type":"oil_change","scheduled_date":"2024-01-20"}'

# View real-time tracking
open http://localhost:8080

Conclusion 🎯

You’ve built a comprehensive fleet management system on Alpine Linux! From real-time GPS tracking to route optimization and automated maintenance scheduling, you now have all the tools to manage a modern vehicle fleet efficiently. This system helps reduce costs, improve safety, and maximize fleet productivity. Happy tracking! πŸš›βœ¨