S9203 Site to Host Consist Report Decoder Reference
Implementation notes for parsing AAR MSRP Standard S-9203A.V1.1 consist reports ("T-94"). Covers the segments required to walk a file end to end: AEM, RRE, EOT, and EOC.
Scope
Field semantics are summarized for implementation only. Refer to the controlling AAR standard for authoritative definitions.
1. File Model
A T-94 file is a sequence of fixed-order, line-oriented segments. Each segment begins with a 3-character segment ID.
- AEM — one per consist, always first. Train-level header.
- DCL — zero or more, immediately after AEM (GPS / site asset inventory). At least one required in V1.1.
- RRE — one per rail vehicle, always the first segment for that vehicle. Opens the per-vehicle loop.
- Per-vehicle children (TDM, RTD, CEQ, EOT, XSC, THI, HAT, DYx, GEN, ALM, MMR, TST) follow their RRE.
- EOC — one per consist, always last. Carries the byte count.
Maintenance reports (AMH / MTS / EMS) are a separate message that may arrive attached to the consist or standalone.
1.1 Encoding Variants — Detect Before Parsing
Three output modes are user-selectable at the reader, so a decoder must handle all three.
| Mode | Field widths | Delimiter | Parse strategy |
|---|---|---|---|
| 1 | Fixed | None | Slice by offset table |
| 2 | Fixed | Present (space or *) |
Slice by offset, skip 1 delimiter char between fields |
| 3 | Variable | Present | Split on delimiter, index by position |
Detection heuristic
Read the first line. If characters 4 and 10 (1-based) are the delimiter candidate and total line length equals the fixed-width sum plus delimiter count, treat as Mode 2. If total length equals the bare fixed-width sum, Mode 1. Otherwise Mode 3.
Sample the * character explicitly — V1.1 examples for DCL and TDM use asterisk delimiting, while older examples use space.
Elements and whole segments may be turned off by site configuration, and turning off elements forces a delimited format. Never assume a fixed line length without validating it; treat a short line as "elements suppressed," not as corrupt data.
2. AEM — Consist Header
One per consist. Establishes reporting site, timestamp, direction, speeds, counts, and site health.
2.1 Offset Table
Mode 1 (fixed width, no delimiters), 0-based offsets, total 78 bytes.
| Ref | Off | Len | Type | Field |
|---|---|---|---|---|
AEM00 |
0 |
3 |
AN | Segment ID = "AEM" |
AEM01 |
3 |
5 |
AN | AAR Billing Code |
AEM02 |
8 |
7 |
AN | Site ID |
AEM03 |
15 |
6 |
N | Event Start Date (YYMMDD) |
AEM04 |
21 |
4 |
N | Event Start Time (HHMM, local) |
AEM05 |
25 |
4 |
N | Event Stop Time (HHMM) |
AEM06 |
29 |
3 |
N | Time Zone offset from GMT |
AEM07 |
32 |
1 |
A | Daylight Savings Time Indicator (Y/N) |
AEM08 |
33 |
3 |
AN | Data Format Version Number |
AEM09 |
36 |
4 |
N | Train Sequence Number |
AEM10 |
40 |
1 |
AN | Locomotive Conversion Status |
AEM11 |
41 |
1 |
AN | Railcar Conversion Status |
AEM12 |
42 |
1 |
A | Direction of Travel |
AEM13 |
43 |
1 |
N | Switch / Direction Indicator |
AEM14 |
44 |
1 |
A | Units of Measure (E/M) |
AEM15 |
45 |
3 |
N | Maximum Speed |
AEM16 |
48 |
3 |
N | Minimum Speed |
AEM17 |
51 |
3 |
N | Average Speed |
AEM18 |
54 |
1 |
AN | Movement Status |
AEM19 |
55 |
1 |
AN | Termination Status |
AEM20 |
56 |
1 |
AN | Transmission Type |
AEM21 |
57 |
1 |
AN | Adjacent Track Occupied (Y/N) |
AEM22 |
58 |
5 |
N | Train Length |
AEM23 |
63 |
1 |
AN | Equipment Status Code |
AEM24 |
64 |
2 |
N | Locomotive Count |
AEM25 |
66 |
2 |
N | Locomotives Tagged |
AEM26 |
68 |
3 |
N | Railcar Count |
AEM27 |
71 |
3 |
N | Railcars Tagged |
AEM28 |
74 |
4 |
N | Total Axle Count |
2.2 Implied Decimals
AEM06 Time Zone "##.#" decimal assumed -> "080" = 8.0 hrs west of GMT
5 = Eastern 6 = Central 7 = Mountain 8 = Pacific
AEM08 Format Version "#.##" decimal assumed -> "400" = 4.00
digit 1 = breaking change
digit 2 = backward-compatible enhancement
digit 3 = processing upgrade
Gate version-specific parsing on digit 1 of AEM08. A change in digit 1 may not be backward compatible; digits 2 and 3 are safe to ignore.
2.3 Code Lookups
AEM10 / AEM11 — Conversion Status (consist confidence level)
| Code | Meaning |
|---|---|
G |
Good — no significant problems |
A |
At least one questionable axle pattern was found |
D |
Excessive disqualifiers — 25% or more show tag/axle inconsistency |
M |
Multiple inconsistencies (both A and D) |
? |
Other |
AEM12 — Direction of Travel: N, S, E, W
AEM13 — Switch / Direction Indicator: 0–9, owner-defined. Reflects local switch position so the host can infer route where multiple routes exist. Do not hardcode meanings; make it a per-site lookup.
AEM14 — Units of Measure: E = English, M = Metric. Applies to the entire report. Latch this value and use it for all downstream unit conversions (see Section 6).
AEM18 — Movement Status
| Code | Meaning |
|---|---|
A |
Through movement, over 5 mph |
B |
Through movement, under 5 mph |
C |
Through movement, stop and go |
D |
Through movement, switching (direction changes detected) |
E |
Simulation (software-generated train) |
F |
Reverse exit (pull by) |
R |
Arrival listing |
? |
Other |
Filter E (simulation) out of production analytics. It is a software-generated train, not traffic.
AEM19 — Termination Status
| Code | Meaning | Decoder action |
|---|---|---|
N |
Normal | Accept |
T |
Time-out; train stopped, partial consist reported | Hold as fragment; expect an appended retransmission |
O |
Presence forcibly cleared (failed presence circuit) | Accept, flag data quality |
P |
Processing limitation | Accept, flag data quality |
? |
Other | Accept, flag |
AEM20 — Transmission Type
| Code | Meaning | Decoder action |
|---|---|---|
F |
First transmission | Insert |
R |
Retransmission | Idempotent upsert on consist key |
S |
Summary | Upsert |
A |
Appended | Replace the prior fragment entirely |
? |
Other | Log |
Time-out / append pairing — the main state-machine trap
When AEM19 = T, the site reports a partial train. When movement resumes the train is reported again with the new cars appended, carrying the same train sequence number and the same start date/time as the original, and AEM20 = A.
The appended report replaces the earlier fragment — it is not a delta. Key idempotency on (AEM01, AEM02, AEM09, AEM03, AEM04) and overwrite rather than merge.
AEM23 — Equipment Status Code (site health)
Reflects abnormal site activity during or before this train. Detail lives in the maintenance report.
| Code | Condition | Severity |
|---|---|---|
G |
No problems detected | OK |
A |
Antenna warning — low tag count on one or more antennas | Warning |
B |
Antenna fatal — at least one antenna no longer reading | Fatal |
C |
Communication error with external components | Error |
E |
Intertrack communication warning (recovered) | Warning |
F |
Intertrack communication fatal — cannot reach external processor | Fatal |
H |
Power supply warning — charger temporarily lost power | Warning |
I |
Power supply fatal — charger lost power | Fatal |
K |
Software setup failure — parameter set inaccurately | Error |
M |
Multiple problems | Escalate |
P |
Presence circuit warning (recovered) | Warning |
Q |
Presence circuit fatal — no longer functional | Fatal |
R |
Reader / RF warning (recovered) | Warning |
S |
Reader / RF fatal — at least one reader no longer functional | Fatal |
T |
Wheel transducer warning (recovered) | Warning |
U |
Wheel transducer fatal | Fatal |
V |
No wheel transducer input | Fatal |
W |
External detector trigger (hut door, thermocouple) | Info |
X |
External processor communication warning (recovered) | Warning |
Y |
External processor communication fatal | Fatal |
Z |
Security — excessive failed logon attempts | Security |
? |
Other | Warning |
This field is the natural hook for automated site-health alerting: map to a severity enum on ingest and raise on anything in {B, F, I, Q, S, U, V, M}.
2.4 Derived Read-Rate Metrics
loco_read_rate = AEM25 / AEM24 (Locomotives Tagged / Locomotive Count) railcar_read_rate = AEM27 / AEM26 (Railcars Tagged / Railcar Count)
Counts include untagged equipment; "Tagged" counts only valid tags. Guard against divide-by-zero — a locomotive-only or railcar-only consist yields a zero denominator on the other pair.
3. RRE — Rail Equipment
Opens the per-vehicle loop. At least one RRE per rail vehicle, tagged or not. Always the first segment reported for a vehicle.
3.1 Offset Table
Mode 1 (fixed width, no delimiters), 0-based offsets, total 38 bytes.
| Ref | Off | Len | Type | Field |
|---|---|---|---|---|
RRE00 |
0 |
3 |
AN | Segment ID = "RRE" |
RRE01 |
3 |
3 |
N | Sequence Number (standing order, 1 = first) |
RRE02 |
6 |
1 |
A | Equipment Group Code |
RRE03 |
7 |
4 |
A | Owner Code (SCAC) |
RRE04 |
11 |
10 |
N | Owner Equipment Number |
RRE05 |
21 |
1 |
A | Orientation |
RRE06 |
22 |
1 |
AN | Reserved for future use |
RRE07 |
23 |
1 |
A | Axle Conversion Code |
RRE08 |
24 |
1 |
A | Tag Status |
RRE09 |
25 |
1 |
A | Tag Detail Status |
RRE10 |
26 |
2 |
N | Handshakes Antenna 0 |
RRE11 |
28 |
2 |
N | Handshakes Antenna 1 |
RRE12 |
30 |
3 |
N | Speed of Vehicle |
RRE13 |
33 |
3 |
N | Axles counted by wheel sensor |
RRE14 |
36 |
2 |
N | Platform Count |
3.2 Code Lookups
RRE02 — Equipment Group Code: D = Locomotive, R = Railcar (includes former non-revenue rail), ? = Unknown. Derived from axle pattern when the vehicle is untagged.
RRE05 — Orientation (which end faces forward)
| Code | Railcar | Locomotive |
|---|---|---|
A |
"A" end | "F" end |
B |
"B" end (also called the brake end) | "R" end |
U |
Unknown / not determined | Unknown / not determined |
RRE07 — Axle Conversion Code: G = good, no problem determining vehicles from axle input. B = bad, axle data may have created a phantom vehicle. S = sequence logically correct but software cannot determine vehicle type.
RRE08 — Tag Status (placement and read completeness)
| Code | Meaning |
|---|---|
G |
Good — both right and left tags read |
M |
Mismatched identification — owner code and equipment number differ between tags |
L |
Left tag missing |
R |
Right tag missing |
N |
No tag read |
? |
Other |
RRE09 — Tag Detail Status (tag content and programming)
| Code | Meaning |
|---|---|
K |
OK |
A |
Axle error — tag axle count differs from axle pattern |
E |
EGC error — tag equipment group code differs from axle pattern |
L |
Length error — tag length differs from axle pattern by more than 15% |
O |
Placement error — tag applied on wrong side or wrong end |
D |
Unused tag read on equipment |
W |
Window violation — tag outside the AAR acceptable application window |
M |
Multiple errors |
? |
Other |
RRE09 = "M" triggers a required TDM segment
When Tag Detail Status is M, the RRE is followed by a TDM segment enumerating exactly which cross-checks failed. A decoder that ignores TDM loses all detail behind an "M".
Note also that a TDM is unconditionally required directly after the first RRE in every consist. That one declares which cross-checks the site is capable of measuring at all, so parse it before interpreting any RRE09 value.
3.3 The Duplicate-Sequence Rule
Sequence Number is not a unique key. When two tags are found on one vehicle with differing IDs (an ID mismatch), two RRE segments are emitted with the same RRE01, both carrying RRE08 = "M".
The primary key for a vehicle row must be (consist key, RRE01, occurrence) — or (consist key, RRE01, RRE03, RRE04). A naive upsert on sequence number silently drops the second tag and destroys the mismatch evidence.
3.4 Field Handling Notes
- RRE03 (Owner Code) — 4 characters, space-padded SCAC. Blank if not tagged. Trim on read; distinguish blank-because-untagged from blank-because-suppressed by checking RRE08 = N.
- RRE04 (Equipment Number) — 10 digits with leading zeros. Zero if not tagged. Store as a string if you need exact round-tripping; convert to integer only for matching.
- RRE10 / RRE11 (Handshakes) — 2 digits, saturating: 99 means "99 or more," not literally 99. Do not average across a saturated field without flagging. A zero on one antenna alongside a healthy count on the other is the normal signature of a missing left or right tag.
- RRE14 (Platform Count) — always 1 for non-articulated cars. A value greater than 1 indicates an articulated multi-platform car, which is what CEQ platform codes hang off.
- Untagged vehicles still produce an RRE derived purely from axle pattern. Expect blank SCAC, zero equipment number, and RRE08 = N.
4. EOT — End of Train Device
One per EOT tag read. Usually the last tag in the consist, but it can appear on any vehicle (for example, dead-headed on a locomotive), so do not use EOT position as an end-of-train marker.
4.1 Offset Table
Mode 1 (fixed width, no delimiters), 0-based offsets, total 26 bytes.
| Ref | Off | Len | Type | Field |
|---|---|---|---|---|
EOT00 |
0 |
3 |
AN | Segment ID = "EOT" |
EOT01 |
3 |
3 |
N | Sequence Number of the carrying vehicle |
EOT02 |
6 |
1 |
A | Equipment Group Code (from tag; "E" per EGC table) |
EOT03 |
7 |
4 |
A | Owner Code (SCAC) |
EOT04 |
11 |
10 |
N | Owner Equipment Number |
EOT05 |
21 |
2 |
N | Handshakes Antenna 0 |
EOT06 |
23 |
2 |
N | Handshakes Antenna 1 |
EOT07 |
25 |
1 |
A | Tag Detail Status |
4.2 EOT07 — Tag Detail Status
| Code | Meaning |
|---|---|
K |
OK |
H |
Performance — low handshakes |
W |
Window violation — tag in wrong location |
? |
Other |
Per the implementation-status notes in the standard, EOT Tag Detail Status is always "K" in practice, with all other fields complete. Treat a non-K value as an anomaly worth logging rather than a routine branch.
Maximum occurrences per RRE loop: 99. EOT01 associates the device to its carrier — join on sequence number, and do not assume the EOT belongs to the highest-numbered vehicle.
5. EOC — End of Consist
Mandatory terminating segment. Always the last segment of the consist report. Total 13 bytes.
| Ref | Off | Len | Type | Field |
|---|---|---|---|---|
EOC00 |
0 |
3 |
AN | Segment ID = "EOC" |
EOC01 |
3 |
10 |
N | Total Byte Count |
Byte count semantics
EOC01 counts from the consist header (AEM) up to but not including the EOC segment itself. Use it as the integrity check on every consist:
observed = byte_length(bytes from start of AEM through end of
the segment immediately preceding EOC)
if observed != int(EOC01):
quarantine(consist)
Two things to pin down against live data from each site before enabling hard rejection: whether line terminators (CR/LF) are included in the count, and whether trailing delimiters are counted. These vary by vendor implementation.
Recommended rollout: log the delta in advisory mode first, then enforce once the per-vendor convention is confirmed.
Absence of EOC means a truncated transmission. Quarantine the consist; do not partially commit.
6. Units of Measure
AEM14 governs the whole report. Fields affected across the four segments in scope:
| Segment | Field | E (English) | M (Metric) |
|---|---|---|---|
| AEM | Maximum / Minimum / Average Speed | miles per hour | kilometers per hour |
| AEM | Train Length | feet | decimeters |
| RRE | Speed | miles per hour | kilometers per hour |
Metric train length is in decimeters, not meters. This is a frequent off-by-10 source. Normalize to a single internal unit at ingest and store the original AEM14 value alongside it for audit.
Out of scope here but on the same latch: CEQ length (feet / decimeters), XSC weight (pounds / kilograms), DYL fuel volume (100 gallons / 100 liters) and cumulative energy (100 HP-hours / 100 kW-hours).
7. Parser Contract
7.1 Ordering and Looping
Segments must be transmitted in the standard T-94 sequence. Relevant structure for the segments in scope:
| Segment | Loop L1 | Loop L2 | Status | Max Occurs |
|---|---|---|---|---|
AEM |
1 | 1 | Required | 1 |
DCL |
1 | 1 | Optional loop | 999 |
RRE |
2 | 1 | Optional — begins RRE loop | 999 |
TDM |
2 | 1 | Required after RRE01 / conditional | — |
EOT |
2 | 2 | Optional | 99 |
RTD |
2 | 6 | Mandatory for Class 1 in V1.1 | 99 |
CEQ |
2 | 13 | Optional — begins CEQ loop inside RRE loop | 999 |
MMR |
2 | 14 | Optional — begins MMR loop inside RRE loop | 999 |
EOC |
3 | 1 | Required | 1 |
Loop mechanics: the first segment of a loop appears exactly once per occurrence and never again inside that loop, so RRE reliably delimits vehicle boundaries, and CEQ and MMR reliably delimit their nested occurrences.
7.2 Recommended State Machine
EXPECT_AEM
-> AEM : latch units(AEM14), version(AEM08), consist key,
site health(AEM23); start byte counter
EXPECT_DCL_OR_RRE
-> DCL* : accumulate site asset inventory
-> RRE : first RRE -> next segment MUST be TDM (capability decl.)
IN_VEHICLE
-> RRE : close prior vehicle, open new.
if RRE01 == previous RRE01
-> ID-mismatch pair, SAME vehicle
-> TDM : cross-check detail (required when prior RRE09 == "M")
-> RTD : raw tag hex, 0..99 per vehicle
-> EOT | CEQ | XSC | THI | HAT | DYx | GEN | ALM | MMR | TST
: attach to current RRE01
-> EOC : close vehicle, validate byte count, emit consist
DONE
7.3 Defensive Rules
- Dispatch on segment ID, never on line position. Any segment may be disabled at the site.
- Treat "?" as a first-class value in every enumerated field. It means "other," not "invalid." Never throw on it.
- Preserve raw lines. Store the original segment text alongside the parsed row for the retention window. Reprocessing beats re-requesting from a Class I host.
- Never reject a consist over an unknown code value. Log unknown enum values and pass the record through with a data-quality flag. New codes get added to the standard.
- Idempotency key: (AEM01 Billing Code, AEM02 Site ID, AEM09 Sequence, AEM03 Start Date, AEM04 Start Time). AEM09 wraps from 9999 back to 1 and zero is invalid, so sequence alone is not unique across a long feed.
- Timestamps are local time. AEM03 and AEM04 plus the AEM06 offset plus the AEM07 DST flag. Convert to UTC on ingest and retain the original triple. Do not infer DST from the date; trust AEM07.
- Fixed-width examples in the standard are not perfectly consistent in their spacing. Build the parser off the field-size tables, not off the printed examples, and be tolerant of stray whitespace.
- Numeric fields are zero-padded strings. Strip leading zeros only at the point of use. Equipment numbers in particular should round-trip as strings.
7.4 Validation Checklist per Consist
| Check | Source | On failure |
|---|---|---|
| Segment ID recognized | 3-char prefix | Log and skip segment, continue |
| AEM present and first | State machine | Reject consist |
| EOC present and last | State machine | Quarantine as truncated |
| Byte count matches | EOC01 | Advisory, then reject once convention confirmed |
| Format version major digit supported | AEM08 digit 1 | Route to quarantine for review |
| RRE count reconciles to AEM24 + AEM26 | Counts | Data-quality flag, do not reject |
| Tagged counts do not exceed total counts | AEM24/25, AEM26/27 | Data-quality flag |
| Site health nominal | AEM23 | Raise maintenance alert on fatal codes |
| Every RRE01 within 1..AEM24+AEM26 | Sequence | Data-quality flag |
| Duplicate RRE01 also carries RRE08 = M | Mismatch rule | Log unexpected duplicate |
8. Open Items
- Confirm per-vendor CR/LF and trailing-delimiter treatment in the EOC01 byte count before enforcing rejection.
- Confirm whether source sites emit the V1.1-mandatory RTD and TDM segments, or legacy output without them. This determines whether their absence is an error or an expected legacy condition.
- Register the delimiter character actually used per site (space vs asterisk) in site configuration rather than detecting it per file.
- DCL and TDM are marked Proposed in the standard while RTD is mandatory in V1.1. Verify current adoption state against the latest published revision before treating any of the three as guaranteed.
9. References
- AAR Manual of Standards and Recommended Practices, Standard S-9203A.V1.1 — AEI Site-to-Host Consist Report Format (formerly S-918A). Adopted 2003, revised 2020.
- AAR MSRP Standard S-9203 (latest version) — tag data formats. Appendices A and B for RRE, Appendix E for EOT, Appendix P for raw reader-to-processor data (RTD).
- Appendix A, Section 1.0 of S-9203A — Equipment Group Code values.
This page is an implementation summary. The AAR MSRP is licensed, copyrighted material