A comprehensive routing and scheduling application designed for home service providers (pest control, HVAC, plumbing, etc.) with GPS tracking, multi-route optimization, and intelligent assignment logic.
- Multi-Route Optimization: Optimizes routes for multiple technicians simultaneously
- Intelligent Job Assignment: Considers skills, vehicle equipment, service types, and time windows
- Real-Time GPS Tracking: WebSocket-based live location tracking for all technicians
- Interactive Dashboard: React-based dashboard with map visualization
- Distance Matrix Caching: Reduces API calls and improves performance
- Constraint-Based Optimization: Respects technician skills, vehicle capacity, and service requirements
- Multiple Algorithm Support:
- Nearest Neighbor for initial route construction
- 2-Opt improvement for route refinement
- Constraint satisfaction for skills and equipment matching
- Optimization Criteria:
- Minimize total distance
- Minimize total time
- Balance workload across technicians
- Respect time windows
- Match required skills and equipment
- Real-time technician location tracking
- Google Maps API integration (with Haversine fallback)
- Route visualization on interactive maps
- GPS history tracking and playback
- Node.js with TypeScript
- Express.js for REST API
- Socket.io for real-time GPS tracking
- PostgreSQL with PostGIS for geospatial data
- Google Maps API for distance/duration calculations
- React with TypeScript
- Vite for build tooling
- React Leaflet for map visualization
- TailwindCSS for styling
- TanStack Query for data fetching
- Zustand for state management
schedule/
├── src/ # Backend source code
│ ├── server.ts # Main server entry point
│ ├── types/ # TypeScript type definitions
│ ├── database/ # Database connection and schema
│ ├── repositories/ # Data access layer
│ ├── services/ # Business logic
│ │ ├── distanceService.ts # Distance calculation & caching
│ │ ├── routeOptimizer.ts # Route optimization engine
│ │ └── gpsTrackingService.ts # Real-time GPS tracking
│ ├── controllers/ # API controllers
│ └── routes/ # API routes
├── client/ # Frontend React application
│ ├── src/
│ │ ├── components/ # React components
│ │ │ ├── MapView.tsx # Live GPS map
│ │ │ ├── JobList.tsx # Job management
│ │ │ ├── TechnicianList.tsx # Technician management
│ │ │ └── RouteOptimizer.tsx # Route optimization UI
│ │ ├── App.tsx # Main app component
│ │ └── main.tsx # Entry point
│ └── package.json
├── package.json
├── tsconfig.json
└── README.md
- Node.js 18+ and npm
- PostgreSQL 14+ with PostGIS extension
- Google Maps API key (optional, will fall back to Haversine)
# Clone the repository
git clone <repository-url>
cd schedule
# Install backend dependencies
npm install
# Install frontend dependencies
cd client
npm install
cd ..# Create PostgreSQL database
createdb routing_db
# Enable PostGIS extension
psql routing_db -c "CREATE EXTENSION postgis;"
# Run schema migration
psql routing_db < src/database/schema.sqlCreate a .env file in the root directory:
# Server Configuration
PORT=3000
NODE_ENV=development
# Database Configuration
DB_HOST=localhost
DB_PORT=5432
DB_NAME=routing_db
DB_USER=postgres
DB_PASSWORD=your_password
# Google Maps API (optional)
GOOGLE_MAPS_API_KEY=your_google_maps_api_key
# Routing Configuration
MAX_ROUTES_PER_DAY=50
MAX_STOPS_PER_ROUTE=25
DEFAULT_SERVICE_DURATION_MINUTES=60
ROUTE_START_TIME=08:00
ROUTE_END_TIME=18:00
# GPS Tracking
GPS_UPDATE_INTERVAL_MS=30000# Terminal 1: Start backend server
npm run dev
# Terminal 2: Start frontend development server
cd client
npm run devThe backend API will be available at http://localhost:3000
The frontend dashboard will be available at http://localhost:3001
POST /api/jobs
Content-Type: application/json
{
"customerId": "customer-uuid",
"serviceType": "general_pest",
"location": {
"latitude": 37.7749,
"longitude": -122.4194,
"address": "123 Main St, San Francisco, CA"
},
"estimatedDurationMinutes": 60,
"priority": 5,
"requiredSkills": [
{
"serviceType": "general_pest",
"level": "junior",
"certified": true
}
],
"requiredEquipment": ["sprayer", "chemicals"],
"status": "pending"
}GET /api/jobs?status=pending&serviceType=general_pestPATCH /api/jobs/:id
Content-Type: application/json
{
"status": "completed"
}POST /api/technicians
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com",
"phone": "555-0123",
"skills": [
{
"serviceType": "general_pest",
"level": "senior",
"certified": true
}
],
"homeLocation": {
"latitude": 37.7749,
"longitude": -122.4194,
"address": "100 Home St, San Francisco, CA"
},
"maxJobsPerDay": 10,
"active": true
}GET /api/technicians?activeOnly=truePOST /api/technicians/:id/location
Content-Type: application/json
{
"latitude": 37.7849,
"longitude": -122.4294
}POST /api/routes/optimize
Content-Type: application/json
{
"date": "2024-01-15",
"jobIds": ["job-uuid-1", "job-uuid-2"],
"technicianIds": ["tech-uuid-1", "tech-uuid-2"],
"constraints": {
"maxStopsPerRoute": 25,
"maxRouteHours": 10,
"maxTotalDistanceKm": 300,
"requireSkillMatch": true,
"requireEquipmentMatch": true,
"minimizeDistance": true,
"minimizeTime": true,
"balanceWorkload": true
}
}Response:
{
"routes": [
{
"id": "route-uuid",
"date": "2024-01-15",
"technicianId": "tech-uuid-1",
"stops": [...],
"totalDistanceKm": 45.6,
"totalDurationMinutes": 420,
"optimizationScore": 87.5
}
],
"unassignedJobs": [],
"metrics": {
"totalScore": 87.5,
"totalDistanceKm": 45.6,
"totalDurationHours": 7.0,
"computationTimeMs": 234
}
}GET /api/routes?technicianId=tech-uuid&date=2024-01-15&status=in_progresssocket.emit('technician:connect', technicianId)socket.emit('gps:update', {
technicianId: 'tech-uuid',
location: {
latitude: 37.7749,
longitude: -122.4194
},
speed: 45.5,
heading: 90,
timestamp: new Date()
})socket.on('gps:update', (data) => {
console.log('GPS update:', data)
// Update map marker
})socket.on('technician:online', ({ technicianId }) => {
console.log('Technician online:', technicianId)
})
socket.on('technician:offline', ({ technicianId }) => {
console.log('Technician offline:', technicianId)
})socket.emit('gps:all')
socket.on('gps:all:response', (locations) => {
console.log('All technician locations:', locations)
})The system uses a multi-stage optimization approach:
-
Job Assignment Phase
- Filters jobs by skill and equipment requirements
- Assigns jobs to qualified technicians
- Balances workload across available technicians
-
Initial Route Construction
- Uses Nearest Neighbor algorithm
- Starts from technician's home location
- Builds initial route based on proximity
-
Route Improvement
- Applies 2-Opt algorithm for refinement
- Reduces total distance and time
- Respects time windows and constraints
-
Scoring and Validation
- Calculates optimization score (0-100)
- Validates against constraints
- Generates warnings for violations
- Skill Matching: Ensures technician has required skill level
- Equipment Matching: Checks vehicle has necessary equipment
- Time Windows: Respects job time window requirements
- Route Duration: Limits total hours per route
- Route Distance: Limits total kilometers per route
- Stops Per Route: Maximum number of stops allowed
- Workload Balance: Distributes jobs evenly
# 1. Create some pending jobs
curl -X POST http://localhost:3000/api/jobs \
-H "Content-Type: application/json" \
-d '{
"customerId": "customer-1",
"serviceType": "general_pest",
"location": {"latitude": 37.7749, "longitude": -122.4194},
"estimatedDurationMinutes": 60,
"priority": 5,
"status": "pending"
}'
# 2. Optimize routes for today
curl -X POST http://localhost:3000/api/routes/optimize \
-H "Content-Type: application/json" \
-d '{
"date": "2024-01-15",
"constraints": {
"maxStopsPerRoute": 10,
"requireSkillMatch": true
}
}'
# 3. View optimized routes
curl http://localhost:3000/api/routes?date=2024-01-15// Technician mobile app
import io from 'socket.io-client'
const socket = io('http://localhost:3000')
const technicianId = 'tech-uuid'
// Connect
socket.emit('technician:connect', technicianId)
// Send GPS updates every 30 seconds
setInterval(() => {
navigator.geolocation.getCurrentPosition((position) => {
socket.emit('gps:update', {
technicianId,
location: {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
},
speed: position.coords.speed,
heading: position.coords.heading,
timestamp: new Date(),
})
})
}, 30000)- Caches distance calculations in PostgreSQL
- Reduces Google Maps API calls by ~90%
- 24-hour cache duration (configurable)
- Haversine distance as fallback
- Geospatial indexes on all location columns
- Composite indexes for common queries
- Optimized for route lookup and GPS queries
- Batches distance calculations
- Implements exponential backoff
- Falls back to Haversine for failures
# Build backend
npm run build
# Build frontend
cd client
npm run build
cd ..
# Start production server
NODE_ENV=production npm start# Coming soon# Run backend tests
npm test
# Run frontend tests
cd client
npm test- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
MIT License - See LICENSE file for details
For issues and questions:
- Create an issue in the GitHub repository
- Email: support@example.com
- Mobile app for technicians (React Native)
- Advanced algorithms (Genetic Algorithm, Simulated Annealing)
- Traffic-aware routing using real-time data
- Customer portal for job tracking
- Analytics and reporting dashboard
- Multi-depot support
- Break time scheduling
- Recurring job templates
- SMS/Email notifications
- Integration with popular CRM systems
Built with:
- Route4Me and NextBillion.ai as inspiration
- Google Maps API for distance calculations
- OpenStreetMap for map tiles
- PostgreSQL/PostGIS for geospatial data