Monthly traffic-accident forecasting for the city of Munich, served as a REST API.
AccidPre turns the City of Munich's open "Monatszahlen Verkehrsunfälle" dataset into a set of per-category time-series forecasters. Seven NeuralProphet models — one for every combination of accident category and accident type present in the data — are trained offline with a hyper-parameter grid search, exported as artifacts, and exposed through a small FastAPI service that answers "how many accidents are expected in month M of year Y?".
The City of Munich publishes monthly accident counts broken down by category (MONATSZAHL) and
type (AUSPRAEGUNG); the raw file bundled in data/ covers monthly observations from January 2000
onwards. AccidPre does three things:
- Prepare – the raw CSV is cleaned(rows without a value are dropped), the
MONATcolumn is parsed fromYYYYMMinto a real timestamp, and the frame is split into one univariate series per(category, type)pair in the NeuralProphetds/yformat. - Train – every series is fitted with NeuralProphet using an exhaustive grid search over learning rate, normalisation, batch size and yearly seasonality, with German public holidays as an extra regressor. The best configuration per series is refitted and persisted.
- Serve – a FastAPI app loads the matching model artifact, extends the historic frame to the
requested month and returns the rounded point forecast (
yhat1).
- 📈 Seven independent monthly forecasters, one per accident category/type combination.
- 🇩🇪 German public holidays added to every model via
add_country_holidays(country_name='DE'). - 🔎 Full grid search (72 configurations per series) with train / validation / test splits and MAE + RMSE logged for every configuration.
- ⚡ Historical months are answered straight from the prepared CSVs — no model inference needed.
- 🌐 Typed request/response contract via Pydantic, plus a health-check route.
flowchart LR
A["monatszahlen2307_verkehrsunfaelle<br/>_10_07_23_nosum.csv"] --> B["accid_predictor_train.py<br/>clean · parse dates · group"]
B --> C["data/data_<cat>_<type>.csv<br/>ds, y (2000-01 … 2020-12)"]
B --> D["Grid search<br/>NeuralProphet + DE holidays"]
D --> E["models/neuralprophet/<br/>model_<cat>_<type>.np / .pkl"]
D --> F["train_log/neuralprophet/<br/>train.log · loss curves"]
G["POST /predict<br/>{year, month}"] --> H["app/app.py<br/>FastAPI + Pydantic"]
H --> I["accid_predictor.predict_accid()"]
I --> J{"requested month"}
J -->|"before 2000-01"| K["-1"]
J -->|"2000-01 … 2020-12"| C
J -->|"after 2020-12"| E
E --> L["make_future_dataframe → predict<br/>round(yhat1)"]
C --> L
K --> M["{prediction: int}"]
L --> M
| Layer | Technology |
|---|---|
| Forecasting | NeuralProphet (PyTorch Lightning backend) |
| Data wrangling | pandas, NumPy |
| Visualisation | Matplotlib, seaborn, plotly-static |
| API | FastAPI, Pydantic |
| ASGI server | Uvicorn |
| Reverse proxy | nginx (restarted by app/start.sh) |
| Experiment tracking | Python logging → train_log/neuralprophet/train.log |
Source: Monatszahlen Verkehrsunfälle, München Open Data Portal
(file data/monatszahlen2307_verkehrsunfaelle_10_07_23_nosum.csv).
Relevant raw columns: MONATSZAHL, AUSPRAEGUNG, JAHR, MONAT, WERT.
| Series file | MONATSZAHL |
AUSPRAEGUNG |
|---|---|---|
data_alk_ins.csv |
Alkoholunfälle | insgesamt |
data_alk_vug.csv |
Alkoholunfälle | Verletzte und Getötete |
data_flu_ins.csv |
Fluchtunfälle | insgesamt |
data_flu_vug.csv |
Fluchtunfälle | Verletzte und Getötete |
data_ver_ins.csv |
Verkehrsunfälle | insgesamt |
data_ver_mps.csv |
Verkehrsunfälle | mit Personenschäden |
data_ver_vug.csv |
Verkehrsunfälle | Verletzte und Getötete |
Split convention used during training: everything before 2021-01-01 is train + validation
(15 % held out for validation via NeuralProphet.split_df), everything from 2021-01-01 onwards
is the test set.
- Python 3.x
- The trained artifacts in
models/neuralprophet/and the prepared series indata/(both are committed in this repository); optionally nginx, to run the deployment script as-is
git clone https://github.com/NingyueZhou/AccidPre.git
cd AccidPre
pip install -r app/requirements.txtapp/requirements.txt covers the serving side only (uvicorn, fastapi, pydantic, pandas).
Retraining additionally needs neuralprophet, numpy, matplotlib and seaborn.
The predictor resolves ../data/... and ../models/... relative to the working directory, so the
server must be started from inside app/:
cd app
python app.pyThis binds Uvicorn to 127.0.0.1:4343. For a reload-enabled dev server use
uvicorn app:app --reload from the same directory.
app/start.sh is the production entry point used behind a reverse proxy:
./app/start.shIt restarts nginx and then launches Uvicorn on 0.0.0.0 with --proxy-headers and
--forwarded-allow-ips='*' so client IPs survive the proxy hop.
python accid_predictor_train.pyThe script rebuilds the per-series CSVs in data/, runs the grid search, writes loss curves and
per-configuration metrics to train_log/neuralprophet/, and dumps the best model per series into
models/neuralprophet/. accid_predictor_train.ipynb contains the same pipeline in notebook form
with the exploratory plots.
Health check — curl http://127.0.0.1:4343/ returns {"Heathcheck": "OK"}.
Prediction— the body matches app/sample_request.json:
curl -X POST http://127.0.0.1:4343/predict \
-H "Content-Type: application/json" \
-d @sample_request.jsonRequest:
{
"year": 2020,
"month": 10
}Response:
Response — October 2020 lies inside the historic range, so the value is read straight from
data/data_alk_ins.csv (which records 2020-10-01,34.0):
{
"prediction": 34
}Semantics of the returned value:
| Requested month | Behaviour |
|---|---|
| before 2000-01 | returns -1 (outside the data range) |
| 2000-01 … 2020-12 | historic value looked up in the prepared CSV |
| after 2020-12 | NeuralProphet forecast, rounded to the nearest integer |
Interactive OpenAPI docs are available at http://127.0.0.1:4343/docs.
AccidPre/
├── accid_predictor_train.py # data prep + grid search + model export
├── accid_predictor_train.ipynb # same pipeline with exploratory plots
├── app/
│ ├── app.py # FastAPI app: GET / and POST /predict
│ ├── accid_predictor.py # model loading, period math, prediction
│ ├── sample_request.json # example payload
│ ├── requirements.txt # serving dependencies
│ └── start.sh # nginx + uvicorn deployment script
├── data/
│ ├── monatszahlen2307_verkehrsunfaelle_10_07_23_nosum.csv
│ └── data_<category>_<type>.csv # 7 prepared ds/y series
├── models/neuralprophet/
│ └── model_<category>_<type>.np|.pkl
└── train_log/neuralprophet/
├── train.log # metrics for every grid-search run
├── data_<series>.png # series plots
└── loss_<series>.png # train/validation loss curves
Grid search performed per series (accid_predictor_train.py):
| Hyper-parameter | Values searched |
|---|---|
learning_rate |
0.001, 0.008, 0.01, 0.1 |
normalize |
minmax, soft, standardize |
epochs |
300 |
batch_size |
3, 6, 12 |
yearly_seasonality |
True, False |
That is 72 configurations per series, ranked by test RMSE; the best-ranked configuration is
refitted and saved. Per-configuration MAE and RMSE for train, validation and test are recorded in
train_log/neuralprophet/train.log, and the corresponding loss curves are stored alongside it.
- The
/predictendpoint only acceptsyearandmonth; the category and type are fixed to the defaults ofpredict_accid(category='alk',type='ins'). The other six models are trained and shipped but not reachable through the HTTP API yet. - Model artifacts are frozen at the 2020-12 cut-off, so long-horizon requests extrapolate far beyond the last observed month.
- Relative paths (
../data,../models) require the server to run withapp/as the working directory. - No authentication, rate limiting or input upper bound on
year.
- Digital Product School for setting this challenge for aspiring AI engineers.
- München Open Data Portal for publishing the Monatszahlen Verkehrsunfälle dataset.
- The NeuralProphet project for the forecasting library.
No license file is present in this repository.