Skip to main content

Live Data Polling Setup

This guide explains how to set up automated polling of live traffic data APIs for the production Vanroute app.

Overview​

The Vanroute database needs to poll live traffic APIs every 15 minutes to keep incident data current. This ensures your mobile app always has up-to-date information about:

  • Traffic accidents
  • Road closures
  • Floods and bushfires
  • Roadworks
  • Major events

Currently implemented: NSW Live Traffic Hazards (429+ active incidents)

Polling Architecture​

┌─────────────────────────────────────────────────────────┐
│ │
│ NSW Transport API → Polling Script → Supabase │
│ (Every 15 min) poll-traffic.ts Database │
│ │
│ 53 accidents │
│ 1 fire • Fetches data │
│ 335 roadworks • Deduplicates │
│ 41 events • Upserts records │
│ • Marks resolved │
└─────────────────────────────────────────────────────────┘
│
↓
┌──────────────┐
│ Mobile App │
│ (Real-time) │
└──────────────┘

Setup Options​

Choose one based on your deployment environment:

Best for: Most projects, zero infrastructure costs

Setup Steps​

  1. Add secrets to GitHub repository

    Go to: Settings > Secrets and variables > Actions > New repository secret

    Add these secrets:

    SUPABASE_URL=https://your-project.supabase.co
    SUPABASE_SERVICE_KEY=your-service-role-key
    NSW_TRAFFIC_API_KEY=your-nsw-api-key
  2. Enable GitHub Actions

    The workflow file already exists at .github/workflows/poll-traffic.yml

    GitHub Actions will automatically:

    • Run every 15 minutes
    • Poll all NSW traffic endpoints
    • Update your Supabase database
    • Log results (viewable in Actions tab)
  3. Verify it's working

    • Go to Actions tab in GitHub
    • Look for "Poll Live Traffic Data" workflow
    • Check recent runs for success/failure
  4. Manual trigger (optional)

    You can manually trigger a poll from the Actions tab using the "Run workflow" button

Workflow Configuration​

The workflow is defined in .github/workflows/poll-traffic.yml:

name: Poll Live Traffic Data

on:
schedule:
- cron: '*/15 * * * *' # Every 15 minutes
workflow_dispatch: # Allow manual trigger

jobs:
poll-traffic:
runs-on: ubuntu-latest
steps:
- Checkout code
- Setup Node.js
- Install dependencies
- Poll NSW Live Traffic

Pros:

  • ✅ Free (2,000 minutes/month on free plan)
  • ✅ No infrastructure to maintain
  • ✅ Easy monitoring via GitHub UI
  • ✅ Automatic retries on failure

Cons:

  • ⚠️ Requires code to be on GitHub
  • ⚠️ Minimum interval is 5 minutes (we use 15)

Option 2: Vercel Cron Jobs​

Best for: Projects already deployed to Vercel

Setup Steps​

  1. Deploy to Vercel

    npm install -g vercel
    vercel login
    vercel deploy
  2. Add environment variables

    In Vercel dashboard:

    • Go to Project Settings > Environment Variables
    • Add:
      SUPABASE_URL
      SUPABASE_SERVICE_KEY
      NSW_TRAFFIC_API_KEY
      CRON_SECRET (generate a random string)
  3. Configure cron job

    The vercel.json file is already set up:

    {
    "crons": [{
    "path": "/api/poll-traffic",
    "schedule": "*/15 * * * *"
    }]
    }
  4. Deploy and verify

    vercel deploy --prod

    Check logs in Vercel dashboard under Functions > poll-traffic

Pros:

  • ✅ Integrated with your deployment
  • ✅ Good monitoring/logging
  • ✅ Easy to scale

Cons:

  • 💰 Cron jobs require Pro plan ($20/month)
  • ⚠️ Vercel-specific solution

Option 3: Traditional Cron Job (Linux/macOS Server)​

Best for: Self-hosted deployments, existing server infrastructure

Setup Steps​

  1. Run the setup script

    cd packages/database/scripts
    chmod +x setup-cron.sh
    ./setup-cron.sh

    This will:

    • Create a cron job to run every 15 minutes
    • Set up logging to /tmp/vanroute-traffic-poll.log
  2. Verify cron job

    crontab -l

    You should see:

    */15 * * * * /tmp/vanroute-poll-traffic.sh
  3. Monitor logs

    tail -f /tmp/vanroute-traffic-poll.log
  4. Manual run

    cd packages/database
    npm run db:poll-traffic

Pros:

  • ✅ Full control
  • ✅ No external dependencies
  • ✅ Can run more frequently if needed

Cons:

  • ⚠️ Requires maintaining a server
  • ⚠️ Need to handle failures manually
  • ⚠️ Server must stay online 24/7

Option 4: Supabase Edge Functions (Coming Soon)​

Best for: Fully Supabase-native solution

Supabase is adding cron support for Edge Functions. When available, this will be the most integrated option.


Mobile App Integration​

Once polling is set up, your React Native app can access live traffic data through Supabase.

Example: Get Incidents Near User​

import { getIncidentsNearLocation } from '@/services/trafficService';

// Get incidents within 50km of user's location
const incidents = await getIncidentsNearLocation(
userLat,
userLon,
50 // radius in km
);

// Filter critical incidents
const criticalIncidents = incidents.filter(
inc => inc.severity === 'critical'
);

Example: Real-time Updates​

import { subscribeToIncidents } from '@/services/trafficService';

// Subscribe to live updates
const unsubscribe = subscribeToIncidents((incidents) => {
console.log(`Got ${incidents.length} active incidents`);
setIncidents(incidents);
});

// Cleanup on unmount
return () => unsubscribe();

Example: Route Planning​

import { getIncidentsAlongRoute } from '@/services/trafficService';

// Check for incidents along planned route
const routePoints: [number, number][] = [
[-33.8688, 151.2093], // Sydney
[-33.9173, 151.2313], // Checkpoint 1
[-34.0285, 151.1573], // Checkpoint 2
// ... more points
];

const routeIncidents = await getIncidentsAlongRoute(
routePoints,
5 // buffer 5km on each side
);

if (routeIncidents.length > 0) {
console.warn('Route has active incidents:', routeIncidents);
// Show warning to user
// Suggest alternative route
}

See the complete API reference in apps/mobile/services/trafficService.ts


Database Schema​

The live_traffic_incidents table structure:

CREATE TABLE live_traffic_incidents (
id UUID PRIMARY KEY,
geometry geometry(Point, 4326),
incident_type TEXT NOT NULL,
severity TEXT NOT NULL,
title TEXT NOT NULL,
description TEXT,
location_description TEXT,
state TEXT,
road_name TEXT,
start_time TIMESTAMPTZ NOT NULL,
end_time TIMESTAMPTZ,
is_active BOOLEAN DEFAULT true,
affects_caravans BOOLEAN DEFAULT true,
advice TEXT,
data_source TEXT NOT NULL,
source_id TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
resolved_at TIMESTAMPTZ,
UNIQUE(data_source, source_id)
);

Key fields:

  • is_active - Automatically updated by polling script
  • resolved_at - Set when incident becomes inactive
  • affects_caravans - Filter for caravan-relevant incidents
  • severity - low, medium, high, critical

Monitoring & Maintenance​

Check Current Data​

cd packages/database
npm run db:summary

Look for the "Live Traffic Incidents" section.

Manual Poll​

npm run db:poll-traffic

View Active Incidents​

SELECT
incident_type,
severity,
title,
location_description,
created_at
FROM live_traffic_incidents
WHERE is_active = true
ORDER BY severity DESC, created_at DESC;

Incident Statistics​

SELECT
incident_type,
COUNT(*) as count,
MAX(created_at) as latest
FROM live_traffic_incidents
WHERE is_active = true
GROUP BY incident_type
ORDER BY count DESC;

Troubleshooting​

No incidents appearing​

  1. Check API key is valid

    grep NSW_TRAFFIC_API_KEY packages/database/.env
  2. Test API manually

    cd packages/database
    npx tsx scripts/test-nsw-api.ts
  3. Check Supabase connection

    npm run db:summary

GitHub Actions not running​

  1. Check workflow is enabled

    • Go to Actions tab
    • Look for disabled workflows
  2. Verify secrets are set

    • Settings > Secrets and variables > Actions
  3. Check workflow logs

    • Actions tab > Recent workflow runs > Click run > View logs

Incidents not resolving​

The polling script automatically marks incidents as inactive when they no longer appear in the API feed. If this isn't working:

  1. Check the markResolvedIncidents function is running
  2. Verify database triggers are working
  3. Check for duplicate source_id values

Data Sources​

Currently Implemented​

SourceEndpointCoverageUpdate Frequency
NSW Live Traffic/incident/openNSW-wide accidentsReal-time
NSW Live Traffic/flood/openNSW-wide floodsReal-time
NSW Live Traffic/fire/openNSW-wide firesReal-time
NSW Live Traffic/roadwork/openNSW-wide roadworksReal-time
NSW Live Traffic/majorevent/openNSW-wide eventsReal-time

Future Data Sources​


Cost Analysis​

  • Cost: Free (2,000 minutes/month)
  • Usage: ~2 minutes per run = ~4,320 minutes/month (need paid plan for high-frequency polling)
  • Upgrade: $4/month for Pro (50GB storage, 3,000 minutes)

Vercel Cron​

  • Cost: $20/month (Pro plan required for cron)
  • Includes: Unlimited cron jobs, better monitoring

Self-Hosted Server​

  • Cost: Variable (AWS t3.micro ~$10/month, or existing infrastructure)
  • Advantages: Full control, no rate limits

Recommendation: Start with GitHub Actions. It's free for moderate usage and easy to set up.


Production Checklist​

Before going live, ensure:

  • Polling is set up (GitHub Actions, Vercel, or cron)
  • All environment variables are configured
  • API keys are stored securely as secrets
  • Test polling runs successfully
  • Mobile app can query incidents
  • Real-time subscriptions work
  • Monitoring/logging is in place
  • Backup polling method configured (in case primary fails)

Performance Optimization​

Database Indexes​

Already created for optimal query performance:

-- Active incidents
CREATE INDEX idx_live_traffic_active
ON live_traffic_incidents (is_active, start_time)
WHERE is_active = true;

-- Spatial queries
CREATE INDEX idx_live_traffic_geometry
ON live_traffic_incidents USING GIST (geometry);

-- Type filtering
CREATE INDEX idx_live_traffic_type
ON live_traffic_incidents (incident_type, severity);

Mobile App Caching​

Implement caching in your mobile app to reduce database queries:

// Cache incidents for 5 minutes
const CACHE_DURATION = 5 * 60 * 1000;
let cachedIncidents: TrafficIncident[] = [];
let lastFetch: number = 0;

export async function getActiveIncidents(): Promise<TrafficIncident[]> {
const now = Date.now();

if (now - lastFetch < CACHE_DURATION && cachedIncidents.length > 0) {
return cachedIncidents;
}

const incidents = await fetchFromSupabase();
cachedIncidents = incidents;
lastFetch = now;

return incidents;
}

Security Best Practices​

  1. Never commit API keys

    • Use environment variables
    • Add .env to .gitignore
  2. Use Supabase Row Level Security (RLS)

    -- Allow public read access to active incidents
    CREATE POLICY "Active incidents are publicly readable"
    ON live_traffic_incidents FOR SELECT
    USING (is_active = true);
  3. Secure cron endpoints

    • Use CRON_SECRET for Vercel
    • Restrict GitHub Actions to your repository
  4. Rate limiting

    • NSW API: 5 requests/second, 60,000/day
    • Current usage: ~5 requests every 15 min = ~480/day ✅

Support​

For issues or questions:


Last Updated: October 2025 Status: ✅ Production Ready Maintainer: Database Team