Locomotive Digital Twin
Fleet telemetryApril 2026
Spot component wear before a breakdown
Kazakhstan Railways (KTZ) asked for a digital twin of the KZ8A electric and TE33A diesel locomotives. The twin had to take in telemetry continuously, estimate accumulated wear per component, and warn the dispatcher early about overheating, catenary voltage drops or a turbocharger failure.
Onboard unit, broker and dispatch
Each locomotive runs in its own container: a FastAPI service simulates sensors, scores risk per component and publishes packets to MQTT. The dispatch server subscribes to an EMQX broker, aggregates data into PostgreSQL once a minute, and serves REST and WebSocket to the dashboard.
- Python
- FastAPI
- MQTT (EMQX)
- WebSocket
- PostgreSQL
- Docker
if risk_type == "linear_hi":
warn: float = comp_cfg["warn"]
crit: float = comp_cfg["crit"]
if value <= warn:
return 0.0
if value >= crit:
return 1.0
return _curve((value - warn) / (crit - warn), exponent)- 01 / 03
MQTT between locomotive and dispatch
A locomotive publishes to loco/{id}/telemetry and does not know who reads it. A new consumer connects to the broker with no change on board.
- 02 / 03
Component thresholds in config
Thresholds and risk type for each component live in YAML: rising above normal, falling below it, or leaving a band. An exponent shapes the curve, so small deviations barely count toward wear.
- 03 / 03
Aggregates instead of the raw stream
The dashboard gets a live WebSocket stream, while the database stores one aggregate every 60 seconds. History stays compact.
Lesson
What I would do differently
I never found the locomotives' technical documentation, although it was publicly available, so I set the thresholds without it. Competing teams went deeper into how the locomotives work and had stronger visuals. Next time I will start with the domain documentation and only then write code.