Forecast maritime traffic from AIS data, then operate the model through an MLOps workflow.
AIS Traffic Ops is an MLOps project for short-term maritime traffic forecasting. It converts AIS inflow/outflow data into 66x66 spatial grids at 5-minute intervals, trains forecasting models, compares model versions, serves the production model through FastAPI, and monitors the system with Prometheus, Grafana, MLflow, and a custom dashboard.
This repository is the GitHub-ready version of the project. Raw AIS data, large .npz files, full trained model bundles, Prometheus WAL data, and SQLite inference logs are intentionally excluded. The repository keeps the source code, configuration files, evaluation summaries, and dashboard captures needed to understand and reproduce the system structure.
- Preprocesses AIS inflow/outflow arrays into 66x66 grid sequences.
- Experiments with ConvLSTM, temporal attention, multi-head attention, spatial UNet, and gated UNet variants.
- Tracks model versions from
v1throughv16. - Manages the active production model through a
productionsymlink. - Supports hot reload for model replacement without restarting the API server.
- Provides serving, monitoring, model registry, inference logs, and dashboard views.
The current production model is v16, using the multihead_spatial_unet_gated architecture.
flowchart LR
A["AIS raw arrays"] --> B["Preprocess<br/>scaling + sliding windows"]
B --> C["Train<br/>ConvLSTM / Attention / UNet"]
C --> D["Versioned bundle<br/>model + scalers + metadata"]
D --> E["Evaluation<br/>actual grid metrics"]
E --> F["Promotion<br/>production symlink"]
F --> G["FastAPI serving"]
G --> H["Prometheus / Grafana"]
G --> I["SQLite inference logs"]
D --> J["Dashboard / reports"]
Model quality is evaluated against actual grid values, not only training loss. eval_compare.py inverse-transforms both y_true and y_pred back to the original inflow/outflow scale, applies the same 0.05 serving threshold, and compares the actual and predicted traffic grids.
| Metric | Worst | Best | Production v16 | v16 vs worst | Best vs worst |
|---|---|---|---|---|---|
| R2 | v3 0.0511 | v16 0.4972 | 0.4972 | +873.8% | +873.8% |
| SSIM | v3 0.5550 | v16 0.7751 | 0.7751 | +39.6% | +39.6% |
| Occupancy F1 | v3 0.1653 | v15 0.5608 | 0.5485 | +231.7% | +239.2% |
| Inflow MAE | v7 0.0508 | v11 0.0232 | 0.0398 | +21.6% | +54.4% |
| Outflow MAE | v11 0.0759 | v9 0.0191 | 0.0300 | +60.5% | +74.8% |
| Validation MAE | v14 0.4515 | v1 0.1409 | 0.1511 | +66.5% | +68.8% |
R2, SSIM, and Occupancy F1 are the most useful metrics for comparing architectures in this project. val_loss is not used as the main cross-version ranking metric because the loss function changed across experiments.
See Actual-value based evaluation for the evaluation details. Full exported metrics are available in model_metrics.json and model_metrics.csv.
The following media was captured from the running dashboard at http://192.168.2.209:8501.
MP4 version:
Main dashboard views:
| System connections | Model registry | Model architecture |
|---|---|---|
![]() |
![]() |
![]() |
| Training convergence | Prediction comparison |
|---|---|
![]() |
![]() |
.
├── src/ais_traffic/ # data, model, and metric utilities
├── mlops/ # training, serving, promotion, and dashboard scripts
├── docs/media/ # dashboard screenshots, GIF, and MP4
├── docs/reports/ # exported experiment metrics
└── scripts/ # helper scripts such as dashboard capture
Install the base package:
python3 -m venv .venv
source .venv/bin/activate
pip install -e .Run the API server:
pip install -e '.[serve]'
cd mlops
uvicorn serve:app --host 0.0.0.0 --port 8080Train a model:
pip install -e '.[train]'
cd mlops
python3 train.py --config config/short_term_spatial_unet_gated_365_v16.yamlRun the local MLOps stack:
cd mlops
docker-compose up -dGET /health: service health and model load statusPOST /predict: next-step inflow/outflow predictionPOST /admin/reload-model: reload the active production model bundleGET /model/info: active model metadataGET /metrics: Prometheus metrics





