{"data_flow":[{"from":"TfNSW GTFS APIs","label":"GTFS-R positions/trips and static GTFS zip","to":"GTFS Feed"},{"from":"AIS receiver or LAN","label":"UDP 2101 NMEA messages","to":"AIS Feed"},{"from":"GTFS Feed","label":"route enrichment and GTFS status","to":"AIS Feed"},{"from":"AIS Feed","label":"current locations, history, Socket.IO snapshots","to":"Vessel Tracker Web"},{"from":"GTFS Feed","label":"routes, GTFS-R status, static GTFS status","to":"Vessel Tracker Web"},{"from":"4Trak","label":"scheduled timetable override fetches","to":"4Trak Allocation"},{"from":"GTFS files","label":"run-to-route matching","to":"4Trak Allocation"},{"from":"GTFS Feed + 4Trak Allocation","label":"operator timetable and vessel work","to":"Vessel Timetable"},{"from":"Metrics and JSON APIs","label":"optional observability, not this web dashboard","to":"Grafana or Prometheus"}],"generated_at":1778989024346,"generated_at_iso":"2026-05-17T03:37:04.346000+00:00","host_ports":[{"port":"5000:5000","service":"tracker"},{"port":"2101:2101/udp","service":"ais"},{"port":"5003:5003","service":"timetable"}],"internal_ports":[{"port":"5001","service":"gtfs"},{"port":"5002","service":"ais"},{"port":"5004","service":"fourtrak"}],"observability":{"local_assets":[{"path":"data/grafana/dashboards/fleet-deployment.json","purpose":"Existing import/provisioning dashboard for fleet deployment."}],"recommendation":"Use one shared Grafana instance with a separate folder, data sources, and dashboard provisioning for this vessel tracker.","when_separate_is_reasonable":["Different security boundary or audience.","Different retention/availability requirements.","A version/plugin conflict that cannot be solved cleanly in the existing Grafana."],"why":["The project already exposes Prometheus text metrics on GTFS and AIS plus JSON endpoints for allocation tables.","A second Grafana adds another login surface, another backup/upgrade path, and duplicated alerting configuration.","Keeping dashboards separate inside one Grafana preserves purpose without splitting the platform."]},"services":[{"apis":[{"audience":"browser","auth":"none","link":"/","method":"GET","path":"/","purpose":"Live vessel tracker map UI."},{"audience":"browser","auth":"none","link":"/dashboard","method":"GET","path":"/dashboard","purpose":"Web dashboard for services, Compose details, APIs, data flow, and observability notes."},{"audience":"browser","auth":"none","link":"/operator","method":"GET","path":"/operator","purpose":"Operator health page for AIS freshness, GTFS state, DB, workers, and access logs."},{"audience":"browser/internal","auth":"none","link":"/api/dashboard/services","method":"GET","path":"/api/dashboard/services","purpose":"Machine-readable service catalogue used by the dashboard."},{"audience":"browser/internal","auth":"none","link":"/api/vessels","method":"GET","path":"/api/vessels","purpose":"Current vessel snapshot for polling clients."},{"audience":"browser/internal","auth":"none","link":"/api/locations","method":"GET","path":"/api/locations","purpose":"Canonical feed-agnostic location payload with source metadata."},{"audience":"browser/internal","auth":"none","link":"/api/routes","method":"GET","path":"/api/routes","purpose":"Route overlay geometry and metadata with ETag support."},{"audience":"browser/internal","auth":"none","link":"/api/history","method":"GET","path":"/api/history","purpose":"Position history. Supports hours and optional mmsi filters."},{"audience":"operator","auth":"X-Api-Key","method":"POST","path":"/api/history/clear","purpose":"Clear persisted position history."},{"audience":"operator/internal","auth":"X-Api-Key","method":"GET","path":"/api/history/stats","purpose":"History row counts, oldest/newest timestamps, and retention."},{"audience":"browser/internal","auth":"none","link":"/api/fleet_timeline","method":"GET","path":"/api/fleet_timeline","purpose":"Compact fleet coverage buckets for history/availability panels."},{"audience":"operator/internal","auth":"none","link":"/api/gtfsr/status","method":"GET","path":"/api/gtfsr/status","purpose":"GTFS-Realtime status, proxied from the GTFS feed when configured."},{"audience":"operator/internal","auth":"none","link":"/api/gtfs/static/status","method":"GET","path":"/api/gtfs/static/status","purpose":"Static GTFS download/load status, proxied from the GTFS feed when configured."},{"audience":"operator/browser","auth":"optional X-Api-Key for access detail","link":"/api/operator/status","method":"GET","path":"/api/operator/status","purpose":"Consolidated operator health for the status page."},{"audience":"operator","auth":"X-Api-Key","method":"GET","path":"/api/access/recent","purpose":"Recent HTTP access attempts."},{"audience":"operator","auth":"X-Api-Key","method":"GET","path":"/api/access/stats","purpose":"Aggregated access-log statistics by source, path, and status."},{"audience":"operator/debug","auth":"X-Api-Key","method":"GET","path":"/api/access/source","purpose":"Shows how the app resolves the current source IP and proxy headers."},{"audience":"Compose","auth":"none","link":"/healthz","method":"GET","path":"/healthz","purpose":"Worker liveness for Docker health checks."},{"audience":"Compose","auth":"none","link":"/readyz","method":"GET","path":"/readyz","purpose":"Database readiness probe."}],"command":"gunicorn --workers 1 --threads 100 --timeout 120 --bind 0.0.0.0:5000 wsgi_tracker:app","compose":{"depends_on":["sydney-ferries-gtfs-feed","sydney-ferries-ais-feed"],"expose":[],"healthcheck":"GET /healthz on container port 5000 every 30s","ports":["5000:5000"]},"data":[{"path":"./data/routes.json:/app/routes.json:ro","purpose":"Static fallback route geometry."},{"path":"./data/templates:/app/templates:ro","purpose":"HTML templates for tracker, operator status, and dashboard pages."},{"path":"./data:/app/data","purpose":"SQLite access log/history data and shared runtime data."}],"environment":[{"name":"SECRET_KEY","purpose":"Flask session/security secret."},{"name":"CLEAR_API_KEY","purpose":"Protects destructive history-clear and access-detail APIs."},{"name":"TRACKER_PORT","purpose":"HTTP port, 5000 in Compose."},{"name":"HISTORY_DB","purpose":"Tracker access-log database path."},{"name":"GTFS_FEED_URL","purpose":"Internal GTFS feed service URL."},{"name":"LOCATION_AIS_FEED_URL","purpose":"Internal AIS location feed URL."},{"name":"LOCATION_FEED_TYPE","purpose":"Internal alias for the tracker map location source."},{"name":"TRACKER_MAP_LOCATION_SOURCE","purpose":"Tracker map vessel position source: AIS, GTFS, Both, or None."},{"name":"ACCESS_LOG_*","purpose":"HTTP access logging, retention, and proxy trust configuration."}],"health":{"checked_at":1778989024346,"detail":"Workers and tracker DB are healthy.","label":"Healthy","latency_ms":null,"ok":true},"id":"tracker","image":"local Dockerfile","kind":"web app","name":"sydney-ferries-vessel-tracker","purpose":"Primary browser map for live vessel locations, route overlays, location-source selection, history playback, operator status, and this service dashboard.","restart":"unless-stopped","status":{"mode":"local"},"title":"Vessel Tracker Web"},{"apis":[{"audience":"internal","auth":"none","method":"GET","path":"/api/gtfs/cache","purpose":"Compact GTFS cache used by AIS and tracker enrichment."},{"audience":"Grafana/internal","auth":"none","method":"GET","path":"/api/gtfs/vehicles","purpose":"Matched GTFS vehicles as a simple row list."},{"audience":"internal","auth":"none","method":"GET","path":"/api/gtfs/locations","purpose":"GTFS-R vehicles in the tracker's canonical location shape."},{"audience":"internal","auth":"none","method":"GET","path":"/api/locations","purpose":"Alias for GTFS-R location payload."},{"audience":"Grafana/internal","auth":"none","method":"GET","path":"/api/gtfs/fleet-summary","purpose":"Counts for available/matched GTFS vehicles and routes."},{"audience":"internal","auth":"none","method":"GET","path":"/api/gtfs/timetable","purpose":"Ferry timetable/operations payload used by vessel timetable."},{"audience":"internal/debug","auth":"none","method":"GET","path":"/api/gtfs/route-info","purpose":"Latest GTFS route-info for a supplied MMSI."},{"audience":"internal/debug","auth":"none","method":"GET","path":"/api/gtfs/route-info/<mmsi>","purpose":"Route-info lookup for a single vessel MMSI."},{"audience":"operator/internal","auth":"none","method":"GET","path":"/api/gtfsr/status","purpose":"GTFS-Realtime poll status and counts."},{"audience":"operator/internal","auth":"none","method":"GET","path":"/api/gtfs/static/status","purpose":"Static GTFS zip/cache/extract state."},{"audience":"browser/internal","auth":"none","method":"GET","path":"/api/routes","purpose":"Route overlay payload generated from GTFS/static fallback."},{"audience":"Prometheus/Grafana","auth":"none","method":"GET","path":"/metrics","purpose":"Prometheus text metrics for GTFS-R/static GTFS health."},{"audience":"Compose","auth":"none","method":"GET","path":"/healthz","purpose":"Worker liveness."},{"audience":"Compose/operator","auth":"none","method":"GET","path":"/readyz","purpose":"GTFS readiness and cache status."}],"command":"gunicorn --workers 1 --threads 8 --timeout 900 --bind 0.0.0.0:5001 wsgi_gtfs_feed:app","compose":{"depends_on":[],"expose":["5001"],"healthcheck":"GET /healthz on container port 5001 every 30s","ports":[]},"data":[{"path":"./data/routes.json:/app/routes.json:ro","purpose":"Static fallback route geometry."},{"path":"./data/gtfs:/app/gtfs","purpose":"Static GTFS zip, extracted files, and compact cache."}],"environment":[{"name":"GTFSR_ENABLED","purpose":"Enables GTFS-Realtime vehicle/trip polling."},{"name":"GTFSR_API_KEY","purpose":"TfNSW API key. Keep secret."},{"name":"GTFSR_VEHICLE_POSITIONS_URL","purpose":"TfNSW ferry VehiclePositions feed."},{"name":"GTFSR_TRIP_UPDATES_URL","purpose":"Optional TripUpdates feed URL."},{"name":"GTFS_STATIC_*","purpose":"Static GTFS download, extraction, cache, and daily schedule settings."},{"name":"ROUTES_CACHE_SECONDS","purpose":"Route overlay response cache duration."}],"health":{"checked_at":1778989024346,"detail":"Health endpoint returned ok.","label":"Healthy","latency_ms":14.5,"ok":true,"url":"http://sydney-ferries-gtfs-feed:5001/healthz"},"id":"gtfs","image":"local Dockerfile","kind":"internal API","name":"sydney-ferries-gtfs-feed","purpose":"Polls TfNSW GTFS-Realtime, maintains static GTFS files/cache, builds route/timetable payloads, and exposes JSON plus Prometheus metrics for other services.","restart":"unless-stopped","status":{"mode":"http","url":"http://sydney-ferries-gtfs-feed:5001"},"title":"GTFS Feed"},{"apis":[{"audience":"receiver/network","auth":"network boundary","method":"UDP","path":"2101/udp","purpose":"AIS NMEA input from host or LAN."},{"audience":"internal","auth":"none","method":"GET","path":"/api/vessels","purpose":"Compatibility vessel snapshot for tracker adapter."},{"audience":"internal","auth":"none","method":"GET","path":"/api/locations","purpose":"Canonical AIS location payload."},{"audience":"operator/internal","auth":"none","method":"GET","path":"/api/ais/status","purpose":"AIS ingest, workers, DB, and GTFS enrichment status."},{"audience":"internal/browser","auth":"none","method":"GET","path":"/api/history","purpose":"Persisted AIS position history."},{"audience":"operator","auth":"X-Api-Key","method":"POST","path":"/api/history/clear","purpose":"Clear AIS position history."},{"audience":"operator/internal","auth":"none","method":"GET","path":"/api/history/stats","purpose":"AIS history row counts and retention."},{"audience":"internal/browser","auth":"none","method":"GET","path":"/api/fleet_timeline","purpose":"Compact AIS fleet coverage buckets."},{"audience":"operator/internal","auth":"none","method":"GET","path":"/api/gtfsr/status","purpose":"GTFS enrichment status visible to AIS."},{"audience":"Prometheus/Grafana","auth":"none","method":"GET","path":"/metrics","purpose":"Prometheus text metrics for AIS worker and vessel counts."},{"audience":"browser/internal","auth":"none","method":"Socket.IO","path":"/socket.io","purpose":"Live AIS vessel snapshots and updates."},{"audience":"Compose","auth":"none","method":"GET","path":"/healthz","purpose":"Worker liveness."},{"audience":"Compose","auth":"none","method":"GET","path":"/readyz","purpose":"Database readiness."}],"command":"gunicorn --workers 1 --threads 100 --timeout 120 --bind 0.0.0.0:5002 wsgi_ais_feed:app","compose":{"depends_on":["sydney-ferries-gtfs-feed"],"expose":["5002"],"healthcheck":"GET /healthz on container port 5002 every 30s","ports":["2101:2101/udp"]},"data":[{"path":"./data:/app/data","purpose":"AIS history SQLite database and shared app data."}],"environment":[{"name":"SECRET_KEY","purpose":"Flask/Socket.IO app secret."},{"name":"CLEAR_API_KEY","purpose":"Protects history-clear endpoint."},{"name":"AIS_FEED_PORT","purpose":"HTTP API port, 5002 internally."},{"name":"HISTORY_DB","purpose":"AIS history database path."},{"name":"LOCATION_SEEN_BUCKET_SECONDS","purpose":"Coalesces location coverage history buckets."},{"name":"GTFS_FEED_URL","purpose":"Internal GTFS feed used for route enrichment."}],"health":{"checked_at":1778989024346,"detail":"HTTP Error 503: SERVICE UNAVAILABLE","label":"Issue","latency_ms":18.2,"ok":false,"url":"http://sydney-ferries-ais-feed:5002/healthz"},"id":"ais","image":"local Dockerfile","kind":"ingest API","name":"sydney-ferries-ais-feed","purpose":"Receives AIS NMEA over UDP, decodes current vessel locations, stores position history, enriches with GTFS, and exposes live Socket.IO/API feeds.","restart":"unless-stopped","status":{"mode":"http","url":"http://sydney-ferries-ais-feed:5002"},"title":"AIS Feed"},{"apis":[{"audience":"browser","auth":"none","external":"http://localhost:5003/","method":"GET","path":"/","purpose":"Vessel timetable browser UI."},{"audience":"browser/internal","auth":"none","external":"http://localhost:5003/api/timetable","method":"GET","path":"/api/timetable","purpose":"Merged GTFS timetable plus 4Trak vessel allocation payload."},{"audience":"browser/operator","auth":"none","external":"http://localhost:5003/api/status","method":"GET","path":"/api/status","purpose":"GTFS and allocation service status for the timetable UI."},{"audience":"Compose","auth":"none","method":"GET","path":"/healthz","purpose":"Local process health and dependency URLs."},{"audience":"Compose","auth":"none","method":"GET","path":"/readyz","purpose":"Readiness check that asks the GTFS feed if it is ready."}],"command":"gunicorn --workers 1 --threads 8 --timeout 120 --bind 0.0.0.0:5003 wsgi_vessel_timetable:app","compose":{"depends_on":["sydney-ferries-gtfs-feed","sydney-ferries-4trak-get-vessel-allocation"],"expose":[],"healthcheck":"GET /healthz on container port 5003 every 30s","ports":["5003:5003"]},"data":[],"environment":[{"name":"TIMETABLE_PORT","purpose":"HTTP port, 5003 in Compose."},{"name":"GTFS_FEED_URL","purpose":"Internal GTFS feed URL."},{"name":"TIMETABLE_VESSEL_ALLOCATION_API_URL","purpose":"Internal 4Trak allocation service URL."},{"name":"TIMETABLE_VESSEL_ALLOCATION_SERVICE_TIMEOUT_SECONDS","purpose":"Timeout when reading allocation service."}],"health":{"checked_at":1778989024346,"detail":"Health endpoint returned ok.","label":"Healthy","latency_ms":17.4,"ok":true,"url":"http://sydney-ferries-vessel-timetable:5003/healthz"},"id":"timetable","image":"local Dockerfile","kind":"web app","name":"sydney-ferries-vessel-timetable","purpose":"Browser timetable that combines static/live GTFS with 4Trak allocations so operators can see vessel work, trips, routes, and upcoming services.","restart":"unless-stopped","status":{"mode":"http","url":"http://sydney-ferries-vessel-timetable:5003"},"title":"Vessel Timetable"},{"apis":[{"audience":"timetable/browser","auth":"internal network","method":"GET","path":"/api/4trak/vessel-allocation","purpose":"Full allocation payload with status, columns, rows, cells, and trips."},{"audience":"internal/debug","auth":"internal network","method":"GET","path":"/api/4trak/vessel-allocation/trips","purpose":"Matched allocation trips as row objects."},{"audience":"Grafana/internal","auth":"internal network","method":"GET","path":"/api/4trak/vessel-allocation/grafana/rows","purpose":"Fleet deployment table rows."},{"audience":"Grafana/internal","auth":"internal network","method":"GET","path":"/api/4trak/vessel-allocation/grafana/cells","purpose":"Fleet deployment grid cells."},{"audience":"operator","auth":"internal network","method":"GET","path":"/api/4trak/vessel-allocation/status","purpose":"Configuration, scheduler, cache, and GTFS match status."},{"audience":"Compose","auth":"none","method":"GET","path":"/healthz","purpose":"Service config and scheduler health."}],"command":"gunicorn --workers 1 --threads 4 --timeout 120 --bind 0.0.0.0:5004 wsgi_4trak_allocation:app","compose":{"depends_on":[],"expose":["5004"],"healthcheck":"GET /healthz on container port 5004 every 30s","ports":[]},"data":[{"path":"./data/4trak:/app/data","purpose":"Cached 4Trak allocation payloads."},{"path":"./data/gtfs:/app/gtfs:ro","purpose":"Read-only static GTFS files used to match runs to F routes."}],"environment":[{"name":"FOURTRAK_GET_VESSEL_ALLOCATION_ENABLED","purpose":"Enables/disables 4Trak fetching."},{"name":"TIMETABLE_OVERRIDES_LOGIN_URL","purpose":"4Trak login endpoint."},{"name":"TIMETABLE_OVERRIDES_URL","purpose":"4Trak timetable-overrides endpoint."},{"name":"TIMETABLE_OVERRIDES_USERNAME/PASSWORD","purpose":"4Trak credentials. Treat as secrets."},{"name":"FOURTRAK_GET_VESSEL_ALLOCATION_CACHE_SECONDS","purpose":"Fresh-cache window for API responses."},{"name":"FOURTRAK_GET_VESSEL_ALLOCATION_SCHEDULE_*","purpose":"Scheduled fetch times and jitter."},{"name":"GTFS_STATIC_EXTRACT_DIR","purpose":"GTFS files used for run-to-route matching."}],"health":{"checked_at":1778989024346,"detail":"4trak_get_vessel_allocation","label":"Healthy","latency_ms":14.0,"ok":true,"url":"http://sydney-ferries-4trak-get-vessel-allocation:5004/healthz"},"id":"fourtrak","image":"local Dockerfile","kind":"scheduled API","name":"sydney-ferries-4trak-get-vessel-allocation","purpose":"Fetches 4Trak timetable overrides on a schedule, caches vessel-to-run allocations, joins them to static GTFS, and serves browser/Grafana-friendly JSON tables.","restart":"unless-stopped","status":{"mode":"http","url":"http://sydney-ferries-4trak-get-vessel-allocation:5004"},"title":"4Trak Allocation"}],"summary":{"api_count":57,"healthy_count":4,"host_port_count":3,"internal_port_count":3,"issue_count":1,"service_count":5}}
