I'm building OpenTrace ML, an Apache-2.0 Python and Go library for traffic forecasting, computer-vision road observations, and GPS/map workflows. The latest work makes it possible to compare forecasting models chronologically, choose a model for each forecast lead, train independent candidates in parallel, and export supported models for native Go inference.
Update checked October 24, 2026: PRs #25 and #26 are merged. main is at 2e8e9ac; package metadata is 0.2.0a1 (alpha). This is a source update, not a tagged GitHub release or a PyPI publishing announcement.
Repository · Contributor guide · Roadmap · Contributor discussion
What you can build
- A traffic-count forecast comparison with persistence, daily seasonal, online linear, neural MLP, and horizon-ensemble models.
- A Python training pipeline that exports a bounded JSON model for inference in a Go application.
- A road-observation layer that converts timestamped detections and GPS observations into GeoJSON.
- An experimental trace-processing workflow with consent, pseudonymization, recording-gap segmentation, and reviewed aggregate candidate cells.
These are composable library components. Forecasts represent counts for a time series, not travel times or ETAs. A production routing service, hosted API, and production map matcher remain future application work.
1. Dynamic programming selects models across the forecast horizon
The new HorizonEnsembleForecaster asks a specific question: which candidate should supply each future lead, while avoiding excessive changes between candidates?
It measures each candidate's mean absolute error at each lead using expanding chronological validation folds. Dynamic programming then minimizes:
sum of selected lead-wise validation errors
+ switch_penalty * number of candidate changes
For H forecast leads and M candidates, selection takes O(H × M²) time and O(H × M) memory, rather than enumerating M^H paths. A positive switching penalty discourages changing models; it does not guarantee smooth forecast values.
Only selected candidates are refitted on the full training history. During forecasting, each candidate generates its own recursive trajectory, and the ensemble selects the requested outputs afterward. An ensemble prediction is not fed back into another candidate's lag history.
The selection report exposes lead-wise losses, the selected path, calibration boundaries, and training warnings. Candidate failures are surfaced instead of silently removing a model. Most importantly, calibration scores are selection scores: evaluate the chosen ensemble on a separate future period.
See the ensemble design and runnable example.
2. Parallel CPU training, with measured overhead
Setting n_jobs=2 runs independent candidate calibration/refit work through joblib CPU processes. Folds within a candidate stay sequential; inference is sequential. This is single-machine CPU parallelism, not GPU or distributed neural training.
On the committed synthetic benchmark of 1,440 observations and four neural candidates:
| Measurement | Serial | Two processes |
|---|---|---|
| First fit | 2.32 s | 2.75 s |
| Median of two subsequent warm fits | 2.54 s | 1.46 s |
The warm result is about 1.74× faster on this workload, with matching predictions and selected paths. The first parallel fit was slower. Startup, serialization, memory, and task size matter, so serial execution remains the default. These few measurements are not a universal speedup claim.
The benchmark report records settings, versions, timings, and warnings.
3. The streaming learner now handles realistic count magnitudes
The online model previously combined Huber residual clipping with traffic targets in the thousands. Its input scaler also changed over time while retained coefficients stayed in the old coordinate system.
The repair uses squared-error updates with a normalized per-observation step, and rebases the coefficients/intercept whenever the scaler changes. Changing the scaler alone therefore preserves the raw-input prediction function. Scaling uses only data already available at that update.
Regression tests cover constant signals at 1, 1,000, and 10,000 counts, a 1,000-to-2,000 distribution shift, and scaler-coordinate invariance. The linear inference format remains compatible. Squared loss is less robust to outliers, and the first lags observations are warmup; this is not a promise of accuracy on arbitrary sensors.
4. Train neural models in Python; run them in Go
Portable export now supports the neural MLP and ensembles composed of supported built-in linear, neural, and seasonal models.
The neural JSON contract records lag ordering, calendar features, weights/biases, input and target scalers, history, last timestamp, and cadence. Hidden layers use ReLU; the scalar output is inverse-scaled and clipped at zero. Each forecast becomes the next lag without modifying the stored snapshot.
The loaders validate versions, dimensions, finite values, duplicate fields, and size limits. Inference serialization is data-only: it does not load pickle or executable model code. The Go core stays standard-library-only. Custom Python callbacks are not portable exports.
Version 1 requires explicit UTC or fixed-offset training and integer-second cadence. Naive timestamps and DST-zone exports require explicit preparation. Snapshot parameters and lag history can still be sensitive.
CI checks both original tiny fixtures and actual Python-trained exports in a native Go process. The neural 24-step comparison uses rtol=1e-10 and atol=1e-8 for the tested cases. See the portable neural and ensemble contract.
5. What the seasonal evaluation actually shows
The evaluation plan separates a January 2017 calibration window from four 2018 final windows. Each final window contains 336 uninterrupted hours: 240 initial training hours followed by four non-overlapping 24-hour outer test folds. Every model receives the same outer folds; the ensemble performs its selection inside each training prefix.
The source is Hogue's UCI Metro Interstate Traffic Volume dataset, licensed CC BY 4.0, DOI: 10.24432/C5X60B. The committed report includes checksums, timestamps, model settings, dependencies, per-lead errors, and warnings. The raw data is not bundled.
MAE in traffic counts; lower is better:
| Final window | Persistence | Daily seasonal | Online linear | Neural MLP | Ensemble |
|---|---|---|---|---|---|
| Winter 2018 | 2475.05 | 619.64 | 667.62 | 549.75 | 652.68 |
| Spring 2018 | 2360.28 | 771.49 | 657.30 | 170.86 | 357.81 |
| Summer 2018 | 1987.19 | 735.54 | 613.97 | 338.89 | 482.60 |
| Autumn 2018 | 2146.56 | 508.17 | 694.99 | 468.95 | 547.05 |
The neural candidate has the lowest MAE in these four windows. The ensemble does not beat it in any of them, and the online learner does not consistently beat daily seasonality. A flexible selection algorithm is useful infrastructure, but it does not automatically improve forecasts.
All standalone neural fits reached the fixed iteration cap; those convergence warnings remain in the report. The data comes from one station, and selecting uninterrupted windows can favor better sensor availability. These results do not establish cross-road generalization. Earlier September-only results used a different protocol, so they should not be treated as a controlled before/after comparison.
6. Geospatial safeguards and optional vision integration
TraceCleaningConfig(max_gap_seconds=...) splits retained trace points around long recording gaps. Segments have deterministic zero-based times and retain their offsets from the cleaned trip start. Process them separately so a missing recording interval does not become an artificial connecting edge. Without the setting, the single-trace API is preserved.
Unmatched-trace aggregation now counts distinct trip pseudonyms per quantized cell, requires at least three contributors, deduplicates repeat uploads/points, and suppresses groups below the threshold. GeoJSON export requires an explicit human-review state.
This is not formal anonymity: trips are not unique people, all intermediate traces remain sensitive, and the review flag is a caller attestation. Hosting applications still need access controls, consent verification, retention rules, and an audit trail. Candidate cells are discovery areas, not established road centerlines.
An original synthetic OSM-format fixture now builds a tiny local graph with network calls disabled in CI. The optional RTMDetDetector converts MMDetection output into the common detection contract without making MMDetection a core dependency. Its smoke test uses deterministic mocked CPU output; no real-checkpoint accuracy is claimed and no third-party weights are bundled.
OpenTrace does not automatically edit OpenStreetMap. Observations remain separate application layers, and external data/maps/weights retain their licensing requirements. See data provenance.
Try it locally
Python 3.10–3.12 are tested in CI. From a shell:
git clone https://github.com/vrajpatell/opentrace-ml.git
cd opentrace-ml
python -m venv .venv
source .venv/bin/activate
python -m pip install -e '.[dev]'
python examples/parallel_horizon_ensemble.py --n-jobs 2
python examples/benchmark_traffic_models.py --synthetic-demo
pytest
On Windows PowerShell, activate with .venv\Scripts\Activate.ps1. The examples above use original synthetic data; they demonstrate execution, not real-world accuracy. For the precise code described here, check out commit 2e8e9ace212d8e266385acac2d0dbc99790d24a2 before installing.
Release preparation passed 174 local tests and 61 subtests, including installed optional integrations and Python-to-Go checks. All eight post-merge CI jobs passed: Python 3.10/3.11/3.12, Go 1.26/1.27, the Go aggregate gate, package validation, and offline geo integration. Wheel/sdist metadata and installed-wheel imports were checked. See the readiness analysis.
Where contributors can help next
The seven issues linked in earlier versions of this article are now closed. These are proposed next contributions from the roadmap, not claims that the features already exist:
| Area | A focused starting contribution |
|---|---|
| ML evaluation | Predeclare a second station or dataset and reproduce the same chronological protocol, including losses and warnings. |
| Uncertainty | Propose forecast intervals and evaluate coverage/width separately by lead without using final test data for calibration. |
| Go performance | Add reproducible benchstat comparisons and explain allocation or timing regressions. |
| GIS/map matching | Add an original parallel-road or ambiguous-junction fixture with explicit expected results; propose a bounded Valhalla adapter. |
| Computer vision | Add a documented RDD2022 train/evaluate command with checkpoint provenance and per-class metrics. |
| Examples/docs | Build a small forecast-comparison notebook or MapLibre observation viewer and document every external dependency. |
Start by opening a scoped issue or replying in Discussion #5 with your proposed input, expected output, and a small acceptance test. Reproductions, API review, examples, and documentation are useful contributions alongside code. Please use original fixtures rather than uploading private traces or unlicensed weights.
Which part would you like to work on: Python forecasting, Go inference, computer vision, or geospatial integration?
Disclosure: This update was prepared with AI assistance and checked against the repository code, documentation, evaluation reports, and CI status.
Top comments (0)