Jump to content

S9203 Site to Host Consist Report Decoder Reference

From DocDepot

Implementation notes for parsing AAR MSRP Standard S-9203A.V1.1 consist reports ("T-94"). Covers the four segments required to walk a file end to end: *AEM*, *RRE*, *EOT*, *EOC*.

Field semantics are summarized for implementation only refer to the controlling AAR standard for authoritative definitions and before publishing.

File Model

A T-94 file is a sequence of fixed-order, line-oriented segments. Each segment begins with a 3-character segment ID. This guide covers the basic consist level segments. For a full implementation, refer to AAR S9203.

  • *AEM* — one per consist, always first. Train-level header.
  • *RRE* — one per rail vehicle, always the first segment for that vehicle. Opens the per-vehicle loop.
  • *EOC* — one per consist, always last. Carries the byte count.

Encoding Variants — Detect Before Parsing

Three output modes are user-selectable at the reader, Comet will set up the delimiter character as a default.

|| Mode || Field widths || Delimiter || Parse strategy || | 1 | Fixed | none | Slice by offset table | | 2 | Fixed | present (space or Template:*) | Slice by offset, skip 1 delimiter char between fields | | 3 | Variable | present | Split on delimiter, index by position |

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 Template:* character explicitly.

{note}

  • Elements and whole segments may be turned off* by site configuration. 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.

{note}

AEM — Consist Header

One per consist. Establishes reporting site, timestamp, direction, speeds, counts, and site health.

Offset Table (Mode 1, 0-based, 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 | DST Indicator (Y/N) | | AEM08 | 33 | 3 | AN | Data Format Version | | 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 |

Implied Decimals

{code} 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

                     digit1 = breaking change, digit2 = compatible enhancement,
                     digit3 = processing upgrade

{code}

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.

Code Lookups

  • AEM10 / AEM11 — Conversion Status (confidence)*

|| Code || Meaning || | G | Good, no significant problems | | A | At least one questionable axle pattern | | D | Excessive disqualifiers (≥25% tag/axle inconsistency) | | M | 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 §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 |

{tip} Filter *E* (simulation) out of production analytics. It is a software-generated train, not traffic. {tip}

  • 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 (Billing Code, Site ID, Sequence, Start Date/Time) | | S | Summary | Upsert | | A | Appended | *Replace* the prior fragment entirely | | ? | Other | Log |

{warning:title=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 your idempotency on (AEM01, AEM02, AEM09, AEM03, AEM04) and overwrite rather than merge. {warning}

  • 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 ≥1 antenna | Warning | | B | Antenna fatal — ≥1 antenna no longer reading | Fatal | | C | Communication error with external components | Error | | E | Intertrack comms warning (recovered) | Warning | | F | Intertrack comms 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 | | P | Presence circuit warning (recovered) | Warning | | Q | Presence circuit fatal — no longer functional | Fatal | | R | Reader / RF warning (recovered) | Warning | | S | Reader / RF fatal — ≥1 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 comms warning (recovered) | Warning | | Y | External processor comms fatal | Fatal | | Z | Security — excessive failed logon attempts | Security | | M | Multiple problems | Escalate | | ? | 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}.

Derived Read-Rate Metrics

{code} loco_read_rate = AEM25 / AEM24 (Locomotives Tagged / Locomotive Count) railcar_read_rate = AEM27 / AEM26 (Railcars Tagged / Railcar Count) {code} 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.

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.

Offset Table (Mode 1, 0-based, 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 | | 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 |

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" (brake) end | "R" end | | U | Unknown / not determined | Unknown |

  • 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 / read completeness)*

|| Code || Meaning || | G | Good — both right and left tags read | | M | Mismatched identification (owner code + equipment number differ between tags) | | L | Left tag missing | | R | Right tag missing | | N | No tag read | | ? | Other |

  • RRE09 — Tag Detail Status (tag content / programming)*

|| Code || Meaning || | K | OK | | A | Axle error — tag axle count differs from axle pattern | | E | EGC error — tag EGC differs from axle pattern | | L | Length error — tag length differs from axle pattern by >15% | | O | Placement error — 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 |

{warning:title=RRE09 = "M" triggers a required TDM} 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". Also note a TDM is unconditionally required directly after the *first* RRE in every consist, which declares which cross-checks the site is capable of measuring at all — parse it before interpreting any RRE09 value. {warning}

The Duplicate-Sequence Rule

{warning}

  • 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.

{warning}

h3. 3.4 Field Handling Notes

  • *RRE03 (Owner Code)* — 4 chars, *space-padded* SCAC ("SP  "). *Blank if not tagged.* Trim on read; distinguish blank-because-untagged from blank-because-suppressed by checking RRE08 = N.
  • *RRE04 (Equipment Number)* — 10 digits, leading zeros. *Zero if not tagged.* Store as string if you need to round-trip exactly; convert to integer only for matching.
  • *RRE10 / RRE11 (Handshakes)* — 2 digits, saturating: *99 means "99 or more"*, not literally 99. Do not compute averages 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/right tag.
  • *RRE14 (Platform Count)* — always 1 for non-articulated cars; >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 number, RRE08 = N.

h2. 4. EOT — End of Train Device

One per EOT tag read. Usually the last tag in the consist, but can appear on any vehicle (e.g. dead-headed on a locomotive), so do not use EOT position as an end-of-train marker.

h3. 4.1 Offset Table (Mode 1, 0-based, 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 |

h3. 4.2 EOT07 — Tag Detail Status || Code || Meaning || | K | OK | | H | Performance — low handshakes | | W | Window violation — tag in wrong location | | ? | Other |

{note} Per the implementation-status notes in the standard, EOT Tag Detail Status is *always "K"* in practice; all other fields are complete. Treat non-K as an anomaly worth logging rather than a routine branch. {note}

Max occurrences per RRE loop: 99. EOT01 associates the device to its carrier — join on sequence number, do not assume the EOT belongs to the highest-numbered vehicle.

h2. 5. EOC — End of Consist

Mandatory terminating segment. Always the last segment of the consist report.

|| Ref || Off || Len || Type || Field || | EOC00 | 0 | 3 | AN | Segment ID = "EOC" | | EOC01 | 3 | 10 | N | Total Byte Count |

Total: 13 bytes.

{warning:title=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:

{code} observed = byte_length(all bytes from start of AEM through end of segment preceding EOC) if observed != int(EOC01): reject_or_quarantine(consist) {code}

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. {warning}

Absence of EOC means a truncated transmission — quarantine, do not partially commit.

h2. 6. Units of Measure

AEM14 governs the whole report. Fields affected across the four segments in scope:

|| Segment || Field || E (English) || M (Metric) || | AEM | Max / Min / Avg Speed | mph | kph | | AEM | Train Length | feet | decimeters | | RRE | Speed | mph | kph |

{warning} 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. {warning}

Out of scope here but on the same latch: CEQ length (feet / decimeters), XSC weight (pounds / kilograms), DYL fuel volume (100 gal / 100 L) and cumulative energy (100 HP-hr / 100 kW-hr).

h2. 7. Parser Contract

h3. 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 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 / MMR reliably delimit their nested occurrences.

h3. 7.2 Recommended State Machine

{code} 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 {code}

h3. 7.3 Defensive Rules

  1. *Dispatch on segment ID, never on line position.* Any segment may be disabled at the site.
  2. *Treat "?" as a first-class value in every enumerated field.* It means "other," not "invalid." Never throw on it.
  3. *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.
  4. *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.
  5. *Idempotency key:* (AEM01 Billing Code, AEM02 Site ID, AEM09 Sequence, AEM03 Start Date, AEM04 Start Time). AEM09 wraps 9999 → 1, and zero is invalid — sequence alone is not unique across a long feed.
  6. *Timestamps are local time.* AEM03/AEM04 plus AEM06 offset plus AEM07 DST flag. Convert to UTC on ingest and retain the original triple. Do not infer DST from the date; trust AEM07.
  7. *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.
  8. *Numeric fields are zero-padded strings.* Strip leading zeros only at the point of use; equipment numbers in particular should round-trip as strings.

h3. 7.4 Validation Checklist per Consist

|| Check || Source || On failure || | Segment ID recognized | 3-char prefix | Log + 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 ≤ total counts | AEM24/25, AEM26/27 | Data-quality flag | | Site health nominal | AEM23 | Raise maintenance alert on fatal codes | | Every RRE01 in 1..AEM24+AEM26 | Sequence | Data-quality flag | | Duplicate RRE01 also has RRE08 = M | Mismatch rule | Log unexpected duplicate |

h2. 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 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 Template:*) 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.

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. Appendix A/B for RRE, Appendix E for EOT, Appendix P for raw reader-to-processor data (RTD).
  • Appendix A §1.0 of S-9203A — Equipment Group Code values.
This page is an implementation summary. The AAR MSRP is licensed, copyrighted material — link to the controlled copy rather than attaching or transcribing the source document into the wiki.