Services & mandates¶
Every Databús component has exactly one role and a hard "Does NOT" boundary. This page is the authoritative corrected service map — it supersedes the stale descriptions in AGENTS.md and MODEL.md wherever they disagree with the code.
Stale docs correction
AGENTS.md describes separate realtime-engine/, publisher/, and scheduler/ Python projects. Those top-level directories do not exist. Everything runs as Django apps inside backend/ and Celery workers launched from that same Django project. The sections below reflect the compose services as they actually run.
orchestrator¶
Role: Control plane and domain authority.
Process: Django + Daphne ASGI server. Exposes HTTP and WebSocket.
Django apps loaded:
api— DRF REST endpoints for run commands and telemetry reads.runs— Run domain models, lifecycle FSM service, detection domain.schedule_engine— GTFS Schedule query layer and GTFS-RT builder task definitions.feed— GTFS data models (Agency, Route, Trip, Stop, StopTime, Shape, Calendar).operations— Vehicle and operator models.website— UI-facing views.messages— AMQP event publisher (see caveat below).gtfs— GTFS submodule.
Responsibilities:
- Validates and records run lifecycle commands from the REST API.
- Drives the
RunLifecycleService(which executes FSM transitions synchronously on the HTTP thread for commands). - Persists domain data and lifecycle state in PostgreSQL via Django ORM.
- Exposes DRF token auth, admin, and OpenAPI docs at
/api/docs/. - Sends WebSocket heartbeats via Django Channels after each GTFS-RT rebuild.
Does NOT:
- Process raw MQTT telemetry.
- Perform map-matching or stop-status computation.
- Write vehicle telemetry to Redis.
Emits: Commands (REST → lifecycle service → FSM actions → Redis/DB).
Consumes: REST requests, WebSocket connections.
realtime-engine¶
Role: Real-time reasoning, telemetry ingestion, and lifecycle event processing.
Process: Celery worker draining queue realtime_engine. Also hosts the MQTT consumer as an in-process Celery bootstep (realtime_engine/mqtt.py, class MQTTConsumerStep).
Source: backend/realtime_engine/
Responsibilities:
- Subscribes to
transit/vehicle/+/positionandtransit/vehicle/+/occupancyon NanoMQ (QoS 0) via the bootstep. Theprogressionleaf is not subscribed — progression is computed server-side. - Validates each incoming MQTT message against the telemetry contract and writes it to Redis (
vehicle:<id>:positionor:occupancy). - Updates
runs:last_seen:<run_id>on every message. - Enqueues
process_position_update(run_id, vehicle_id)for heavy work off the paho network thread. process_position_updateruns in four steps:- Server-side stop-status production via
runs.domain.progression.producer.produce_stop_status(real GPS→polyline map-matching incompute.py). - Feeds the computed
vehicle_stop_statusintodetect_from_telemetrywith leaf"progression"for completion detection. - Computes and caches the
stop_time_updatesprojection. - Re-reads the latest position from Redis and runs position-leaf detection (
RunStartedDetector, etc.).
- Server-side stop-status production via
- Processes
run_lifecycle_event(event, payload)tasks — these callRunLifecycleService.process_eventwhich executes FSM guards and actions. - Runs
scan_stale_runsevery 30 seconds (scheduled by thescheduler) to detect telemetry silence.
Does NOT:
- Serve HTTP.
- Build GTFS-RT feeds.
- Own domain schemas.
Queue: realtime_engine
Key tasks (backend/realtime_engine/tasks.py):
- process_position_update(run_id, vehicle_id)
- run_lifecycle_event(event, payload)
- scan_stale_runs()
schedule-engine¶
Role: GTFS Realtime feed projection.
Process: Celery worker draining queue schedule_engine.
Source: backend/schedule_engine/
Responsibilities:
- Reads Redis snapshots of active runs and vehicles.
- Builds GTFS-RT protobuf and JSON outputs for VehiclePositions, TripUpdates, and Alerts.
- Writes output files to
backend/feed/files/(vehicle_positions.{pb,json},trip_updates.{pb,json},alerts.{pb,json}). - Pushes a WebSocket
"status"group message via Django Channels after eachbuild_trip_updatescall.
Does NOT:
- Write to Redis.
- Modify lifecycle state.
- Process MQTT messages.
Queue: schedule_engine
Key tasks (backend/schedule_engine/tasks.py):
- build_vehicle_positions() — every 15 s
- build_trip_updates() — every 15 s
- build_alerts() — every 10 s (currently stub: returns "Feed ServiceAlert built")
AGENTS.md calls this the 'Publisher'
ARCHITECTURE.md §5 and AGENTS.md describe a separate "Publisher" service. In the actual code the projection role is fulfilled by schedule_engine running inside the schedule-engine Celery worker. There is no standalone publisher process.
scheduler¶
Role: Temporal orchestration (Celery Beat).
Process: celery beat launched from the backend/databus/celery.py app.
Responsibilities:
- Fires the four periodic tasks on the configured schedule.
Beat schedule (defined in backend/databus/celery.py):
| Task | Interval |
|---|---|
schedule_engine.tasks.build_vehicle_positions |
every 15 s |
schedule_engine.tasks.build_trip_updates |
every 15 s |
schedule_engine.tasks.build_alerts |
every 10 s |
realtime_engine.tasks.scan_stale_runs |
every 30 s |
Beat schedule is in code, not admin
AGENTS.md states that the beat schedule is managed via django_celery_beat in the admin UI. This is incorrect. The schedule is hardcoded in app.conf.beat_schedule in backend/databus/celery.py and requires a code change to modify.
state (Redis)¶
Role: Authoritative real-time operational state.
Image: redis:7-alpine
Single writer rule: realtime-engine is the sole writer. schedule-engine reads snapshots. orchestrator writes lifecycle state hash fields via FSM actions.
Does NOT:
- Persist historical data.
- Apply business logic.
See state-and-persistence.md and ../data-model/redis-keys.md.
database (PostgreSQL + PostGIS)¶
Role: Durable domain storage and operational history.
Image: Custom build on postgis/postgis.
Contents:
- Domain records: runs, vehicles, operators, GTFS schedule data.
- Run lifecycle events and state transitions.
- GTFS-RT blobs (retained ~1 year).
telemetry-broker (NanoMQ)¶
Role: MQTT broker for vehicle telemetry.
Image: emqx/nanomq:0.24.9-full
Topics served: transit/vehicle/+/{position,occupancy} (QoS 0, no retained messages for telemetry).
In production, Traefik terminates TLS on port 8883 and forwards plain MQTT to NanoMQ on port 1883.
message-broker (RabbitMQ)¶
Role: Celery task queue and AMQP event bus.
Image: rabbitmq:4-management
Primary use: Celery task routing between scheduler, realtime-engine, and schedule-engine.
Designed use (not yet implemented): AMQP domain events on exchange databus.events. See messaging.md.
analytics-engine (Prefect)¶
Role: Batch analytics and modeling.
Image: prefecthq/prefect:3-latest
Operationally independent of the real-time path. Consumes PostgreSQL batch data.
task-monitoring (Flower)¶
Role: Celery task observability dashboard.
Image: mher/flower:2.0
Connects to RabbitMQ via AMQP. Exposed at tasks.<domain> in production.
user-interface (Nuxt)¶
Role: Web frontend for dispatchers and operators.
Image: Built from frontend/Dockerfile.
Communicates with orchestrator via REST and WebSocket.
docs (production only)¶
Role: Serve the built Zensical static site.
Image: nginx:alpine, serving docs/site/.
Only present in compose.prod.yml. Not included in the dev compose.