The "magic" import pipeline that turns a chaotic supplier software delivery into a clean, standardized train-software baseline — and pushes it to the RSSCM backend so everyone works from the same source of truth.
Part of the Hack4Rail 2026 challenge
Rolling Stock Software Configuration Management, built by the tools group.
The data-model / backend lives in the companion repo
OpenRail-Playground/RSSCM.
This project has been initiated during the Hack4Rail 2026, a joint hackathon organised by the railway companies SBB, ÖBB, and DB in partnership with the OpenRail Association.
When a supplier delivers new software for a train, they ship a chaotic bundle: Excel sheets, Word documents, PDFs, firmware blobs, ZIPs — every supplier and every delivery looks different. Someone then has to figure out, by hand:
- What is actually in this delivery?
- Which versions are new compared to what is already on the train?
- What therefore needs to be installed — and nothing more?
Doing this manually is slow and error-prone. In our test delivery a human reviewer counted the changes by hand, missed one, and was misled by inconsistent rows in a secondary table.
The Baseline Builder automates that work in four steps:
- Read the delivery — open the supplier bundle, safely unpack it, and parse every file (Excel / Word / PDF …) into one common structure.
- Read the current train state — what software is on the train today.
- Diff — compare the two and work out exactly which software is new or updated, ignoring duplicates and noise.
- Publish — push that clean baseline to the RSSCM backend (and store any binaries), so the change is recorded against the standard data model.
The result is a clean baseline: a short, trustworthy list of "install these, and only these" — instead of a 500-row spreadsheet.
Train Assets (current train state)
source: CSV export, REST API, or a
previous "Ladeliste"
│
▼
Vendor ──► Software "Ladeliste" ──► ┌─────────────────────────┐
(chaotic ZIP: │ Baseline Builder │
xls, pdf, docx, │ (this repo, "magic") │
csv, exe, bin) └───────────┬─────────────-┘
│ │
REST API: create │ │ binaries, SBOMs
/ update metadata │ ▼
▼ ┌──────────────────┐
┌──────────────┐ │ Asset Storage │
Software Responsible ────► │ Django Webapp │◄────┘ (href links)
Person (reviews) │ (RSSCM API) │
└──────┬───────┘
│ export a single Baseline (standard)
▼
Train ◄── Workshop Personnel (install)
The Django backend is a proof-of-concept of the data model and can be replaced; the Baseline Builder talks to it only through the REST API.
Every parser, no matter the input format, normalizes into the same RSSCM model (mirrors the Django models):
| Entity | Meaning |
|---|---|
Manufacturer |
Who makes the component / software |
Component |
The thing that hosts software (a system component / device) |
SoftwareItem |
A node in the software tree (name + type), can have a parent |
SoftwareRelease |
A concrete version of a SoftwareItem (version, checksum, link, SBOM) |
VehicleType / ConsistType |
Which train / formation the software applies to |
The hierarchy the parsers reconstruct:
Train baseline → Subsystem → System component → { Hardware | Software → Software component }
# 1. Install (uses uv)
uv sync
# 2. See the commands
uv run rsscm-import --help
# 3. Run the full pipeline (dry-run prints what *would* be posted)
uv run rsscm-import run \
--delivery ./Chaos_HackTrain_Ladeliste.zip \
--current-state ./Train_Asset_Baseline_Hacktrain.xlsx \
--dry-runDrop --dry-run to actually post to the backend (default
http://localhost:8000/api/v1/).
| Command | What it does |
|---|---|
ingest <delivery> |
Safely unpack & classify every file (zip-bomb guarded), show SHA-256 |
parse <delivery> |
Parse any delivery into normalized items (JSON) |
current-state <file> |
Load the current train state from a source |
diff --delivery … --current … |
Show NEW / UPDATED / UNCHANGED (deduplicated) |
push --delivery … --current … |
Post the clean baseline to the backend |
run … |
The whole pipeline end-to-end |
Running the pipeline on our test delivery produces this clean baseline of 5 changes (3 software-level + 2 subsystem-level):
| Subsystem | Software | Change |
|---|---|---|
| Air Conditioning (HVAC) | 332 Operating System Software | V4.1 A → V4.8 A |
| Train Control | Operating System | V1.5E → V1.6E |
| Video Surveillance (CCTV) | Kernel Software | G1.5/G2.7 → G1.6/G2.8 |
| Remote Data Transmission (RDA) | (subsystem) | 12.0 → 12.5 |
| Toilet (WC) | (subsystem) | 9.0 → 9.2 |
Pushing the software-level changes creates these records via the REST API (real output):
release_archive_hash,release_archive_linkandsbom_referenceare filled in when the delivery actually contains the binaries + SBOM (e.g. theHVAC_Firmware_*.zipexample). This Ladeliste is metadata-first, so those fields arenulland the binaries (when present) go to Asset Storage.
Software Responsible Person (works in the Django webapp) After the Baseline Builder posts, they see exactly the changes that need review — not 500 unchanged rows. For our delivery:
332 Operating System Software→ V4.8 A on HVAC Front BoxOperating System→ V1.6E on MRB4 (Cab 2)Kernel Software→ G1.6 / G2.8 on Video 1C1D
…each with its version, the component it belongs to, and (when present) the checksum / SBOM / download link for provenance. They review and approve the baseline.
Workshop Personnel (at the train) They receive an exported single baseline (standard format) for a specific train/consist — the actionable "to-do" list:
Install V4.8 A of 332 Operating System Software on the HVAC Front Box; install V1.6E of the Operating System on MRB4; install G1.6/G2.8 of the Kernel Software on Video 1C1D. (plus checksum to verify each file before flashing)
Everything that is already up to date is omitted, so they flash only what changed — "all updates have to be done, otherwise the train is not allowed to leave."
Supplier formats vary from delivery to delivery, so parsing is pluggable:
parsers/base.py— the standard model + a registry that dispatches each file to a parser by type (with a per-component override hook).parsers/xlsx_parser.py,docx_parser.py,pdf_parser.py— one per format, all feeding a shared, header-driven tree normalizer.
Supporting a new format = add one parser and register it. Nothing else changes.
- Zip-bomb guard on ingest (depth / size / count limits).
- Set-based diff: handles the same node appearing many times with mixed versions; reports one change, not dozens of duplicate rows.
- Whitespace-insensitive matching: tolerant of PDF/extraction artifacts that inject spurious spaces into filenames.
- Scoped-tree authority: uses each file's authoritative per-subsystem tree and ignores inconsistent "context" tables — which is how the tool caught a change a manual review missed and avoided two false positives.
uv run pytest # 26 testsIncludes per-parser tests, set-based diff tests, an integration test that locks in the reconciled expected changes, and live API round-trip tests that auto-skip when the backend isn't running.
To run against the live backend (in the companion RSSCM repo):
uv run rsscm/manage.py migrate
uv run rsscm/manage.py runserver- Current-state-from-REST-API reader is stubbed (file/zip export works today).
- Binaries are not yet matched to their metadata item to auto-fill the release checksum/link; asset storage hashes them separately.
- Subsystem/parent linking in the backend tree is flat for now (component-level).
- Intra-document data conflicts (Tree vs context tables) are resolved in favor of the Tree; flagging them as data-quality warnings would be a useful add.
The content of this repository is licensed under the Apache 2.0 license.
