Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
c6bdd0b
feat(eta): package gtfs_eta inference library with relocatable registry
dotjae Jun 25, 2026
3ccfe14
chore(eta): add placeholder global baseline model for dev/test
dotjae Jun 25, 2026
46f226f
feat(eta): generate stop-time updates from ETA predictions
dotjae Jun 25, 2026
6603390
test(eta): stop-time producer suite
dotjae Jun 25, 2026
064e643
chore(eta): drop fake_stop_times placeholder
dotjae Jun 25, 2026
1f8ba1b
feat(feed): publish GTFS Schedule zip from the database
dotjae Jun 27, 2026
c5ef42b
chore(tasks): sketch of the HTTP telemetry fetching bridge
fabianabarca Jul 9, 2026
4c55e5b
feat(models): enhance Company and Operator models, add Equipment and …
fabianabarca Jul 20, 2026
5e1c419
feat(operations): migrate Sensor model and drift reconciliation
dotjae Jul 21, 2026
d4dd3f0
feat(operations): register Sensor in the admin site
dotjae Jul 21, 2026
fdaaf15
chore(config): add MQTT_HOST and MQTT_PORT env vars
dotjae Jul 21, 2026
4b13631
feat(realtime): add pluggable HTTP telemetry source adapter package
dotjae Jul 21, 2026
67cbb72
test(realtime): cover HTTP telemetry source adapters
dotjae Jul 21, 2026
117f394
feat(realtime): add fetch_positions task for HTTP telemetry sources
dotjae Jul 21, 2026
c6c5e37
feat(celery): schedule fetch_positions every 10 seconds
dotjae Jul 21, 2026
3301214
chore(schedule_engine): drop obsolete telemetry fetch sketch
dotjae Jul 21, 2026
567ef68
chore(operations): regenerate migrations at container start
dotjae Jul 21, 2026
6936b30
fix(realtime): gate fetch_positions on current_run, not runs:in_progress
dotjae Jul 21, 2026
d2ca6a3
Merge remote-tracking branch 'origin/main' into feat/fetch-telemetry
dotjae Aug 19, 2026
e16d8fe
Merge remote-tracking branch 'origin/feat/gtfs-schedule-publish' into…
dotjae Aug 19, 2026
fe4266b
chore: gitignore local AI tooling artifacts
dotjae Aug 19, 2026
a3cbb0a
refactor(runs): drop dead RunProgress FSM chain
dotjae Aug 19, 2026
ecf66a8
refactor: remove legacy feed.realtime bridge and unschedule build_ale…
dotjae Aug 19, 2026
8787f0c
chore(docs): drop legacy mkdocs site
dotjae Aug 19, 2026
a6a4ac4
style: remove unused imports and variables (ruff F401/F841)
dotjae Aug 19, 2026
9ccaef8
chore: gitignore published feed output artifacts
dotjae Aug 19, 2026
e8427d8
feat(messages): publish run lifecycle events to databus.events exchange
dotjae Aug 19, 2026
21f913a
build(eta): depend on sibling gtfs-eta repo instead of vendored copy
dotjae Aug 19, 2026
1bfd6a2
chore(eta): re-seed baseline model registry with relocatable paths
dotjae Aug 19, 2026
452ce10
fix(detection): quiet idempotent lifecycle-event re-fires from ERROR …
dotjae Aug 19, 2026
03391fe
fix(realtime): stop fetch_positions from starving the worker pool
dotjae Aug 19, 2026
dded0ff
build(eta): drop direct xgboost dependency (gtfs-eta imports it lazil…
dotjae Aug 19, 2026
3677a01
chore(tooling): enforce docstring and type coverage with ruff D and mypy
dotjae Aug 19, 2026
90dce3d
docs(runs): add one-liner docstrings and complete type hints
dotjae Aug 19, 2026
38d5b3f
docs(realtime_engine): add one-liner docstrings and complete type hints
dotjae Aug 19, 2026
7c4704d
docs(schedule_engine): add one-liner docstrings and complete type hints
dotjae Aug 19, 2026
1a4aa82
refactor(schedule_engine): drop dead EquipmentLogFilter
dotjae Aug 19, 2026
1b88731
docs(feed): add one-liner docstrings and complete type hints
dotjae Aug 19, 2026
b8cd8c7
docs(api): add one-liner docstrings and complete type hints
dotjae Aug 19, 2026
b7bc1fd
refactor(feed): drop legacy one-off fixture scripts
dotjae Aug 19, 2026
ee39467
fix(realtime): skip misconfigured sensors with explicit guards
dotjae Aug 19, 2026
9b6277d
fix(runs): fail fast with a clear error when lifecycle payload lacks …
dotjae Aug 19, 2026
59c29c1
docs(operations): add one-liner docstrings and complete type hints
dotjae Aug 19, 2026
575f2e3
docs(website): add one-liner docstrings and complete type hints
dotjae Aug 19, 2026
c2d41cd
docs(databus): add one-liner docstrings and complete type hints
dotjae Aug 19, 2026
457de06
docs(scripts): add one-liner docstrings and complete type hints
dotjae Aug 19, 2026
5489fe4
fix(api): repair which-shapes and find-trips lookup endpoints
dotjae Aug 19, 2026
487c1b9
fix(api): point CompanySerializer at the real linked_agency field
dotjae Aug 19, 2026
2770788
feat(runs): implement is_run_validated as a revalidation guard
dotjae Aug 19, 2026
1c1d6cf
docs: add READMEs for api, feed, operations, website, messages, datab…
dotjae Aug 19, 2026
0911627
docs: refresh backend, runs, realtime_engine, schedule_engine, script…
dotjae Aug 19, 2026
6db2028
docs(site): update interfaces section against current code
dotjae Aug 20, 2026
5796aaa
docs(site): update runs section against current code
dotjae Aug 20, 2026
03f109c
docs(site): update data-flow section against current code
dotjae Aug 20, 2026
30f599a
docs(site): update operations section against current code
dotjae Aug 20, 2026
b17b18b
docs(site): update architecture, concepts, and data-model sections ag…
dotjae Aug 20, 2026
4228091
docs(site): set production site_url
dotjae Aug 20, 2026
f3595ae
docs(messages): fix envelope example to use real state values
dotjae Aug 20, 2026
efe6aba
docs(messages): drop stale docs-drift note
dotjae Aug 20, 2026
a455ef2
chore: remove dead test stubs and scratch test scripts
dotjae Aug 20, 2026
da5f7b9
refactor(api): drop unused serializers and duplicate field declarations
dotjae Aug 20, 2026
bc0a0d8
style(realtime_engine): move test imports to module top
dotjae Aug 20, 2026
bfd2ad4
test(feed): move exporter smoke tests into collected test package
dotjae Aug 20, 2026
15dcfe1
fix(databus): honor REDIS_PASSWORD in all Redis clients
dotjae Aug 20, 2026
9059f96
docs(operations): update REDIS_PASSWORD note now that clients honor it
dotjae Aug 20, 2026
3c29e43
fix(runs): resolve mypy stragglers in shapes.py and lifecycle guards.py
dotjae Aug 20, 2026
a79b11c
fix(databus): URL-encode Redis password in channel layer address
dotjae Aug 20, 2026
09fd3ff
feat(schedule): hourly GTFS Schedule import from upstream providers
dotjae Aug 20, 2026
28cf925
chore(feed): add TransitSystems and FeedPublisher to allow multi-tena…
fabianabarca Sep 4, 2026
47803cc
feat(cors): add django-cors-headers to settings and configure CORS al…
fabianabarca Sep 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .env.dev
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,13 @@ DEBUG=True
DJANGO_SERVE_STATIC=True
LOG_LEVEL=DEBUG
CREATE_SUPERUSER=True
RUN_MIGRATIONS=True
RUN_MIGRATIONS=True

# MQTT Configuration
MQTT_HOST=telemetry-broker
MQTT_PORT=1883

# ETA model registry (dev default; seed once with seed_baseline_model)
MODEL_REGISTRY_DIR=eta_models
ETA_MAX_STOPS=10
ETA_DEFAULT_UNCERTAINTY_S=120
12 changes: 11 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ REDIS_PASSWORD=redispassword
# Google Maps API Key
API_TOKEN=

# MQTT Configuration
MQTT_HOST=telemetry-broker
MQTT_PORT=1883

# HiveMQ Configuration
HIVEMQ_LOG_LEVEL=INFO

Expand Down Expand Up @@ -54,4 +58,10 @@ FLOWER_DOMAIN=tasks.databus.simovilab.com
DOCS_DOMAIN=docs.databus.simovilab.com

# Certificate in production
CERT_RESOLVER=letsencrypt
CERT_RESOLVER=letsencrypt

# ETA model registry (used by gtfs_eta; set to a writable directory)
# Seed the baseline model once: MODEL_REGISTRY_DIR=eta_models ./.venv/bin/python -m gtfs_eta.seed_baseline_model
MODEL_REGISTRY_DIR=eta_models
ETA_MAX_STOPS=3
ETA_DEFAULT_UNCERTAINTY_S=120
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,12 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# AI tooling artifacts (local only)
graphify-out/
llms.txt
llms-full.txt
CONTEXT_SYNC_NOTES.md

# Published GTFS-RT / Schedule output (runtime artifacts)
backend/feed/files/
17 changes: 17 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.PHONY: lint typecheck test

# ruff needs no Django settings (no settings import), so it runs locally
# against the backend project without going through Docker.
lint:
cd backend && uv run ruff check .

# mypy loads the django-stubs plugin, which imports databus.settings; that
# module reads env vars via python-decouple and fails to import outside the
# dev container, so mypy must run inside it.
typecheck:
docker compose -f compose.dev.yml run --rm orchestrator uv run mypy .

# pytest-django also needs the real settings/DB, so it runs inside the dev
# container too.
test:
docker compose -f compose.dev.yml run --rm orchestrator uv run pytest -q
58 changes: 58 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,61 @@ Django apps:
- `runs`: Run data (_anima machinae_)
- `website`: Miscellaneous website features (e.g., admin interface, users, etc.)
- `feed`: GTFS data handling and processing

## Workspace layout (uv)

This project is a `uv` workspace (see `pyproject.toml` `[tool.uv.workspace]`):

- `gtfs-django`, `gtfs-io` — workspace members, editable installs from the
local `backend/gtfs-django/` and `backend/gtfs-io/` directories.
- `gtfs-eta` — **not** a workspace member. It's an editable *path* dependency
(`[tool.uv.sources]`) pointing at `backend/gtfs-eta`, which is a symlink
(`gtfs-eta -> ../../gtfs-eta`) to the sibling `simovilab/gtfs-eta` repo
checked out next to `databus/`. `compose.dev.yml` bind-mounts
`../gtfs-eta:/gtfs-eta` read-write so the OS-resolved symlink target exists
inside the container too. `gtfs-eta` is excluded from ruff/mypy/pytest here
— it's a separately-maintained codebase with its own lint baseline and test
suite.

## Celery services

Four services share this codebase (see `Dockerfile` build targets and
`compose.dev.yml`):

| Service | Build target | Role | Queue |
|---|---|---|---|
| `orchestrator` | `dev` | Django HTTP + admin + REST API | — (not a worker) |
| `realtime-engine` | `realtime-engine` | Celery worker: MQTT ingestion bootstep, lifecycle events, HTTP polling, staleness scan | `realtime_engine` |
| `schedule-engine` | `schedule-engine` | Celery worker: builds the two GTFS-RT feeds + the daily GTFS Schedule zip | `schedule_engine` |
| `scheduler` | `scheduler` | Celery Beat (`databus/celery.py` `beat_schedule`) | — |

Queues are assigned per-task via `@shared_task(queue=...)`, not Celery
`task_routes`; each worker is started with a matching `-Q <queue>` flag.

## Code quality

Root `Makefile` targets:

```bash
make lint # cd backend && uv run ruff check . — runs locally, no Docker needed
make typecheck # docker compose -f compose.dev.yml run --rm orchestrator uv run mypy .
make test # docker compose -f compose.dev.yml run --rm orchestrator uv run pytest -q
```

`typecheck` and `test` run inside the dev container because Django settings
read env vars via `python-decouple`, which fails outside the container.
`lint` needs no Django settings import, so it runs locally against the
backend project.

Ruff enforces missing-docstring rules (`D1`, i.e. `D100`-`D107`) in addition
to its default `E`/`F` rules. Mypy runs with the `django-stubs` plugin
(`mypy_django_plugin.main`). Both exclude `migrations/`, `gtfs-eta/`, and
`.venv/`.

## Tests

445 tests (`pytest --collect-only`, `backend/`):

```bash
docker compose -f compose.dev.yml run --rm orchestrator uv run pytest -q
```
72 changes: 72 additions & 0 deletions backend/api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# API · public REST layer (DRF)

- **Purpose**: the orchestrator's control plane. Exposes run registration and lifecycle
transitions, read-only GTFS Schedule resources, two lookup endpoints that back the
run-registration UI cascade, and the static realtime OpenAPI schema. Django models: none —
`api` is a REST layer over `runs`, `operations`, and `feed` models.
- **Key modules**:
- `views.py` — all ViewSets/APIViews (operations, runs, GTFS Schedule, auxiliary GTFS)
- `serializers.py` — DRF serializers, including `CreateRunSerializer` / `RunUpdateSerializer`
- `urls.py` — router registrations + custom paths
- `realtime.yml` — static GTFS Realtime/MQTT OpenAPI/AsyncAPI schema, served as a file download

## Run registration & lifecycle

- `POST /api/create-run/` (`CreateRunViewSet`) — validates the payload, resolves
`vehicle_id`/`operator_id`, creates the `Run` row (implicit `RUN_REQUESTED`), then drives it
through `RunLifecycleEvents.VALIDATE_RUN` → `RunLifecycleEvents.INITIALIZE_RUN` via
`RunLifecycleService`. Any failed step returns the failing stage (`serialization`,
`operational_validation`, `registration`, `gtfs_validation`, `initialization`) with a matching
HTTP status (`api/views.py:199-279`).
- `GET /api/runs/<uuid:run_id>/state/` — current `run_lifecycle_state`.
- `POST /api/runs/<uuid:run_id>/update/` (`RunUpdateViewSet`) — advances a run's FSM. The `event`
field takes the **lowercase** `RunLifecycleEvents` value (e.g. `run_confirmed_by_operator`, not
the enum member name); `RunUpdateSerializer` validates against `RunLifecycleEvents` and any
extra `details` are flattened into the payload before `process_event` (`api/views.py:303-366`).
- `GET /api/runs/<uuid:run_id>/history/` — ordered `RunLifecycleTransition` audit log.

## GTFS Schedule + registration-UI lookups

- Router-registered read/write resources for `agency`, `stops`/`geo-stops`, `shapes`/`geo-shapes`,
`routes`, `calendars`, `calendar-dates`, `trips`, `stop-times`, `fare-attributes`, `fare-rules`,
`feed-info` (all backed by `feed.models`, unauthenticated).
- `GET /api/service-today/?date=YYYY-MM-DD` — active GTFS `service_id`s for a date.
- `GET /api/which-shapes/?route_id=` — step 1 of the registration cascade: distinct
`GeoShape`s used by a route's stops in the current (`is_current=True`) feed.
- `GET /api/find-trips/?route_id=&service_id=&shape_id=` — step 2: candidate trips, each tagged
with its matching `Run`'s lifecycle state (or `"UNKNOWN"`).

## Operations resources

Router-registered ViewSets over `operations.models`: `company`, `operator`, `data-provider`,
`vehicle` (filterable by `company`), `equipment`, `equipment-log` (filterable by `equipment`,
`data_provider`, `vehicle`), plus `position`, `stop-status`, `occupancy`, `congestion` from
`runs.models`. All except `CompanyViewSet` use `TokenAuthentication`.

## Auth & docs

- `POST /api/login/` — username/password → DRF auth `Token` + operator basic info.
- Global default authentication is `TokenAuthentication` (`databus/settings.py` `REST_FRAMEWORK`).
- `GET /api/docs/schema/` — serves the **static** file `api/realtime.yml` as a download
(`api/views.py:89-94`); this is a hand-maintained schema, not the dynamic drf-spectacular route.
- `GET /api/docs/` — ReDoc page (`SpectacularRedocView`, clickjacking-exempt so it can be
embedded). Note `drf_spectacular`'s dynamic OpenAPI generation is installed (`SPECTACULAR_SETTINGS`
in `databus/settings.py`) but the `docs/schema/` route intentionally serves the static YAML
instead of the generated schema — the dynamic route is unfinished.

## Configuration

No app-specific env vars; auth/schema settings come from `databus/settings.py`
(`REST_FRAMEWORK`, `SPECTACULAR_SETTINGS`).

## Tests

```
docker compose -f compose.dev.yml run --rm orchestrator uv run pytest api/ -q
```
`api/tests/` covers `company`, `find-trips`, and `which-shapes`; `make test` runs the full suite.

## Docs

- [REST API](../../docs/content/interfaces/rest-api.md)
- [Run lifecycle states](../../docs/content/runs/lifecycle-states.md)
Empty file added backend/api/__init__.py
Empty file.
2 changes: 1 addition & 1 deletion backend/api/admin.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from django.contrib import admin
"""Django admin registrations for the api app (none; api has no models of its own)."""

# Register your models here.
4 changes: 4 additions & 0 deletions backend/api/apps.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"""Django app configuration for the api app."""

from django.apps import AppConfig


class ApiConfig(AppConfig):
"""Configure the api app (DRF ViewSets/serializers exposing GTFS and run data)."""

default_auto_field = "django.db.models.BigAutoField"
name = "api"
2 changes: 1 addition & 1 deletion backend/api/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from django.db import models
"""Django models for the api app (none; api is a REST layer over feed/runs/operations models)."""

# Create your models here.
Loading