Jump to content

S9203 Site to Host Consist Report Decoder Reference: Difference between revisions

From DocDepot
DGast (talk | contribs)
Created page with "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. h2. 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*..."
 
DGast (talk | contribs)
No edit summary
 
(13 intermediate revisions by the same user not shown)
Line 1: Line 1:
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*.
{{DISPLAYTITLE:AEI Site-to-Host Consist Report (T-94) — Decoder Reference}}
Field semantics are summarized for implementation only refer to the controlling AAR standard for authoritative definitions and before publishing.


h2. 1. File Model
Implementation notes for parsing '''AAR MSRP Standard S-9203''' consist reports ("T-94"). Covers the segments required to walk a file end to end: '''AEM''', '''RRE''', '''EOT''', and '''EOC'''.
 
<div style="border-left:4px solid #2e5c8a; background:#eef3f9; padding:0.6em 1em; margin:1em 0;">
'''Scope:'''  Field semantics are summarized for implementation only. Refer to the controlling AAR standard for authoritative definitions.
</div>
 
__TOC__
 
== File Model ==


A T-94 file is a sequence of fixed-order, line-oriented segments. Each segment begins with a 3-character segment ID.
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.
* '''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.
* '''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.
* '''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.
* 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.
* '''EOC''' — one per consist, always last. Carries the byte count.
 
Maintenance reports (AMH / MTS / EMS) are a separate message.
 
=== Encoding Variants — Detect Before Parsing ===


Maintenance reports (AMH / MTS / EMS) are a separate message that may arrive attached to the consist or standalone.
Three output modes are user-selectable at the reader, so a decoder must handle all three.


h3. 1.1 Encoding Variants — Detect Before Parsing
{| class="wikitable"
! Mode !! Field widths !! Delimiter !! Parse strategy
|-
| 1 || Fixed || None || Slice by offset table
|-
| 2 || Fixed || Present (space or <code>*</code>) || Slice by offset, skip 1 delimiter char between fields
|-
| 3 || Variable || Present || Split on delimiter, index by position
|}


Three output modes are user-selectable at the reader, so a decoder must handle all three:
<div style="border-left:4px solid #c77c1e; background:#fdf3e3; padding:0.6em 1em; margin:1em 0;">
'''Detection heuristic'''


|| Mode || Field widths || Delimiter || Parse strategy ||
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.
| 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 |


{warning:title=Detection heuristic}
Sample the <code>*</code> character explicitly
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/TDM use asterisk delimiting, older examples use space.
</div>
{warning}


{note}
<div style="border-left:4px solid #2e5c8a; background:#eef3f9; padding:0.6em 1em; margin:1em 0;">
*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.
'''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.
{note}
</div>


h2. 2. AEM — Consist Header
== AEM — Consist Header ==


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


h3. 2.1 Offset Table (Mode 1, 0-based, total 78 bytes)
=== Offset Table ===
 
Mode 1 (fixed width, no delimiters), 0-based offsets, total '''78 bytes'''.
 
{| class="wikitable"
! Ref !! Off !! Len !! Type !! Field
|-
| <code>AEM00</code> || <code>0</code> || <code>3</code> || AN || Segment ID = "AEM"
|-
| <code>AEM01</code> || <code>3</code> || <code>5</code> || AN || AAR Billing Code
|-
| <code>AEM02</code> || <code>8</code> || <code>7</code> || AN || Site ID
|-
| <code>AEM03</code> || <code>15</code> || <code>6</code> || N || Event Start Date (YYMMDD)
|-
| <code>AEM04</code> || <code>21</code> || <code>4</code> || N || Event Start Time (HHMM, local)
|-
| <code>AEM05</code> || <code>25</code> || <code>4</code> || N || Event Stop Time (HHMM)
|-
| <code>AEM06</code> || <code>29</code> || <code>3</code> || N || Time Zone offset from GMT
|-
| <code>AEM07</code> || <code>32</code> || <code>1</code> || A || Daylight Savings Time Indicator (Y/N)
|-
| <code>AEM08</code> || <code>33</code> || <code>3</code> || AN || Data Format Version Number
|-
| <code>AEM09</code> || <code>36</code> || <code>4</code> || N || Train Sequence Number
|-
| <code>AEM10</code> || <code>40</code> || <code>1</code> || AN || Locomotive Conversion Status
|-
| <code>AEM11</code> || <code>41</code> || <code>1</code> || AN || Railcar Conversion Status
|-
| <code>AEM12</code> || <code>42</code> || <code>1</code> || A || Direction of Travel
|-
| <code>AEM13</code> || <code>43</code> || <code>1</code> || N || Switch / Direction Indicator
|-
| <code>AEM14</code> || <code>44</code> || <code>1</code> || A || Units of Measure (E/M)
|-
| <code>AEM15</code> || <code>45</code> || <code>3</code> || N || Maximum Speed
|-
| <code>AEM16</code> || <code>48</code> || <code>3</code> || N || Minimum Speed
|-
| <code>AEM17</code> || <code>51</code> || <code>3</code> || N || Average Speed
|-
| <code>AEM18</code> || <code>54</code> || <code>1</code> || AN || Movement Status
|-
| <code>AEM19</code> || <code>55</code> || <code>1</code> || AN || Termination Status
|-
| <code>AEM20</code> || <code>56</code> || <code>1</code> || AN || Transmission Type
|-
| <code>AEM21</code> || <code>57</code> || <code>1</code> || AN || Adjacent Track Occupied (Y/N)
|-
| <code>AEM22</code> || <code>58</code> || <code>5</code> || N || Train Length
|-
| <code>AEM23</code> || <code>63</code> || <code>1</code> || AN || Equipment Status Code
|-
| <code>AEM24</code> || <code>64</code> || <code>2</code> || N || Locomotive Count
|-
| <code>AEM25</code> || <code>66</code> || <code>2</code> || N || Locomotives Tagged
|-
| <code>AEM26</code> || <code>68</code> || <code>3</code> || N || Railcar Count
|-
| <code>AEM27</code> || <code>71</code> || <code>3</code> || N || Railcars Tagged
|-
| <code>AEM28</code> || <code>74</code> || <code>4</code> || N || Total Axle Count
|}


|| Ref || Off || Len || Type || Field ||
=== Implied Decimals ===
| 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 |


h3. 2.2 Implied Decimals
<pre>
AEM06  Time Zone      "##.#" decimal assumed  ->  "080" = 8.0 hrs west of GMT
                      5 = Eastern  6 = Central  7 = Mountain  8 = Pacific


{code}
AEM08  Format Version "#.##" decimal assumed  ->  "400" = 4.00
AEM06  Time Zone      "##.#" decimal assumed  ->  "080" = 8.0 hrs west of GMT
                      digit 1 = breaking change
                      5=Eastern 6=Central 7=Mountain 8=Pacific
                      digit 2 = backward-compatible enhancement
AEM08  Format Version "#.##" decimal assumed  ->  "400" = 4.00
                      digit 3 = processing upgrade
                      digit1 = breaking change, digit2 = compatible enhancement,
</pre>
                      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.
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.


h3. 2.3 Code Lookups
=== Code Lookups ===
 
'''AEM10 / AEM11 — Conversion Status (consist confidence level)'''
 
{| class="wikitable"
! Code !! Meaning
|-
| <code>G</code> || Good — no significant problems
|-
| <code>A</code> || At least one questionable axle pattern was found
|-
| <code>D</code> || Excessive disqualifiers — 25% or more show tag/axle inconsistency
|-
| <code>M</code> || Multiple inconsistencies (both A and D)
|-
| <code>?</code> || 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. Units of Measure|Section 6]]).
 
'''AEM18 — Movement Status'''
 
{| class="wikitable"
! Code !! Meaning
|-
| <code>A</code> || Through movement, over 5 mph
|-
| <code>B</code> || Through movement, under 5 mph
|-
| <code>C</code> || Through movement, stop and go
|-
| <code>D</code> || Through movement, switching (direction changes detected)
|-
| <code>E</code> || Simulation (software-generated train)
|-
| <code>F</code> || Reverse exit (pull by)
|-
| <code>R</code> || Arrival listing
|-
| <code>?</code> || Other
|}


*AEM10 / AEM11 — Conversion Status (confidence)*
<div style="border-left:4px solid #3e7d45; background:#eef6ef; padding:0.6em 1em; margin:1em 0;">
|| Code || Meaning ||
Filter '''E''' (simulation) out of production analytics. It is a software-generated train, not traffic.
| G | Good, no significant problems |
</div>
| 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
'''AEM19 Termination Status'''


*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.
{| class="wikitable"
! Code !! Meaning !! Decoder action
|-
| <code>N</code> || Normal || Accept
|-
| <code>T</code> || Time-out; train stopped, partial consist reported || Hold as fragment; expect an appended retransmission
|-
| <code>O</code> || Presence forcibly cleared (failed presence circuit) || Accept, flag data quality
|-
| <code>P</code> || Processing limitation || Accept, flag data quality
|-
| <code>?</code> || Other || Accept, flag
|}


*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).
'''AEM20 Transmission Type'''


*AEM18 — Movement Status*
{| class="wikitable"
|| Code || Meaning ||
! Code !! Meaning !! Decoder action
| A | Through movement, over 5 mph |
|-
| B | Through movement, under 5 mph |
| <code>F</code> || First transmission || Insert
| C | Through movement, stop and go |
|-
| D | Through movement, switching (direction changes detected) |
| <code>R</code> || Retransmission || Idempotent upsert on consist key
| E | Simulation (software-generated train) |
|-
| F | Reverse exit (pull by) |
| <code>S</code> || Summary || Upsert
| R | Arrival listing |
|-
| ? | Other |
| <code>A</code> || Appended || '''Replace''' the prior fragment entirely
|-
| <code>?</code> || Other || Log
|}


{tip}
<div style="border-left:4px solid #c77c1e; background:#fdf3e3; padding:0.6em 1em; margin:1em 0;">
Filter *E* (simulation) out of production analytics. It is a software-generated train, not traffic.
'''Time-out / append pairing — the main state-machine trap'''
{tip}


*AEM19 — Termination Status*
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'''.
|| 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*
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.
|| Code || Meaning || Decoder action ||
</div>
| 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}
'''AEM23 Equipment Status Code (site health)'''
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.
Reflects abnormal site activity during or before this train. Detail lives in the maintenance report.


|| Code || Condition || Severity ||
{| class="wikitable"
| G | No problems detected | OK |
! Code !! Condition !! Severity
| A | Antenna warning — low tag count on ≥1 antenna | Warning |
|-
| B | Antenna fatal — ≥1 antenna no longer reading | Fatal |
| <code>G</code> || No problems detected || OK
| C | Communication error with external components | Error |
|-
| E | Intertrack comms warning (recovered) | Warning |
| <code>A</code> || Antenna warning — low tag count on one or more antennas || Warning
| F | Intertrack comms fatal — cannot reach external processor | Fatal |
|-
| H | Power supply warning — charger temporarily lost power | Warning |
| <code>B</code> || Antenna fatal — at least one antenna no longer reading || '''Fatal'''
| I | Power supply fatal — charger lost power | Fatal |
|-
| K | Software setup failure — parameter set inaccurately | Error |
| <code>C</code> || Communication error with external components || Error
| P | Presence circuit warning (recovered) | Warning |
|-
| Q | Presence circuit fatal — no longer functional | Fatal |
| <code>E</code> || Intertrack communication warning (recovered) || Warning
| R | Reader / RF warning (recovered) | Warning |
|-
| S | Reader / RF fatal — ≥1 reader no longer functional | Fatal |
| <code>F</code> || Intertrack communication fatal — cannot reach external processor || '''Fatal'''
| T | Wheel transducer warning (recovered) | Warning |
|-
| U | Wheel transducer fatal | Fatal |
| <code>H</code> || Power supply warning — charger temporarily lost power || Warning
| V | No wheel transducer input | Fatal |
|-
| W | External detector trigger (hut door, thermocouple) | Info |
| <code>I</code> || Power supply fatal — charger lost power || '''Fatal'''
| X | External processor comms warning (recovered) | Warning |
|-
| Y | External processor comms fatal | Fatal |
| <code>K</code> || Software setup failure — parameter set inaccurately || Error
| Z | Security — excessive failed logon attempts | Security |
|-
| M | Multiple problems | Escalate |
| <code>M</code> || Multiple problems || '''Escalate'''
| ? | Other | Warning |
|-
| <code>P</code> || Presence circuit warning (recovered) || Warning
|-
| <code>Q</code> || Presence circuit fatal — no longer functional || '''Fatal'''
|-
| <code>R</code> || Reader / RF warning (recovered) || Warning
|-
| <code>S</code> || Reader / RF fatal — at least one reader no longer functional || '''Fatal'''
|-
| <code>T</code> || Wheel transducer warning (recovered) || Warning
|-
| <code>U</code> || Wheel transducer fatal || '''Fatal'''
|-
| <code>V</code> || No wheel transducer input || '''Fatal'''
|-
| <code>W</code> || External detector trigger (hut door, thermocouple) || Info
|-
| <code>X</code> || External processor communication warning (recovered) || Warning
|-
| <code>Y</code> || External processor communication fatal || '''Fatal'''
|-
| <code>Z</code> || Security — excessive failed logon attempts || Security
|-
| <code>?</code> || 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 <code>{B, F, I, Q, S, U, V, M}</code>.


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 ===


h3. 2.4 Derived Read-Rate Metrics
<pre>
loco_read_rate    = AEM25 / AEM24    (Locomotives Tagged / Locomotive Count)
railcar_read_rate = AEM27 / AEM26    (Railcars Tagged / Railcar Count)
</pre>


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


h2. 3. RRE — Rail Equipment
== 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.
Opens the per-vehicle loop. At least one RRE per rail vehicle, tagged or not. Always the first segment reported for a vehicle.


h3. 3.1 Offset Table (Mode 1, 0-based, total 38 bytes)
=== Offset Table ===


|| Ref || Off || Len || Type || Field ||
Mode 1 (fixed width, no delimiters), 0-based offsets, total '''38 bytes'''.
| 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 |


h3. 3.2 Code Lookups
{| class="wikitable"
! Ref !! Off !! Len !! Type !! Field
|-
| <code>RRE00</code> || <code>0</code> || <code>3</code> || AN || Segment ID = "RRE"
|-
| <code>RRE01</code> || <code>3</code> || <code>3</code> || N || Sequence Number (standing order, 1 = first)
|-
| <code>RRE02</code> || <code>6</code> || <code>1</code> || A || Equipment Group Code
|-
| <code>RRE03</code> || <code>7</code> || <code>4</code> || A || Owner Code (SCAC)
|-
| <code>RRE04</code> || <code>11</code> || <code>10</code> || N || Owner Equipment Number
|-
| <code>RRE05</code> || <code>21</code> || <code>1</code> || A || Orientation
|-
| <code>RRE06</code> || <code>22</code> || <code>1</code> || AN || Reserved for future use
|-
| <code>RRE07</code> || <code>23</code> || <code>1</code> || A || Axle Conversion Code
|-
| <code>RRE08</code> || <code>24</code> || <code>1</code> || A || Tag Status
|-
| <code>RRE09</code> || <code>25</code> || <code>1</code> || A || Tag Detail Status
|-
| <code>RRE10</code> || <code>26</code> || <code>2</code> || N || Handshakes Antenna 0
|-
| <code>RRE11</code> || <code>28</code> || <code>2</code> || N || Handshakes Antenna 1
|-
| <code>RRE12</code> || <code>30</code> || <code>3</code> || N || Speed of Vehicle
|-
| <code>RRE13</code> || <code>33</code> || <code>3</code> || N || Axles counted by wheel sensor
|-
| <code>RRE14</code> || <code>36</code> || <code>2</code> || N || Platform Count
|}


*RRE02 — Equipment Group Code:* D = Locomotive, R = Railcar (includes former non-revenue rail), ? = Unknown. Derived from axle pattern when the vehicle is untagged.
=== Code Lookups ===


*RRE05 Orientation (which end faces forward)*
'''RRE02 Equipment Group Code:''' D = Locomotive, R = Railcar (includes former non-revenue rail), ? = Unknown. Derived from axle pattern when the vehicle is untagged.
|| 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.
'''RRE05 Orientation (which end faces forward)'''


*RRE08 — Tag Status (placement / read completeness)*
{| class="wikitable"
|| Code || Meaning ||
! Code !! Railcar !! Locomotive
| G | Good — both right and left tags read |
|-
| M | Mismatched identification (owner code + equipment number differ between tags) |
| <code>A</code> || "A" end || "F" end
| L | Left tag missing |
|-
| R | Right tag missing |
| <code>B</code> || "B" end (also called the brake end) || "R" end
| N | No tag read |
|-
| ? | Other |
| <code>U</code> || Unknown / not determined || Unknown / not determined
|}


*RRE09 Tag Detail Status (tag content / programming)*
'''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.
|| 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}
'''RRE08 — Tag Status (placement and read completeness)'''
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}


h3. 3.3 The Duplicate-Sequence Rule
{| class="wikitable"
! Code !! Meaning
|-
| <code>G</code> || Good — both right and left tags read
|-
| <code>M</code> || Mismatched identification — owner code and equipment number differ between tags
|-
| <code>L</code> || Left tag missing
|-
| <code>R</code> || Right tag missing
|-
| <code>N</code> || No tag read
|-
| <code>?</code> || Other
|}


{warning}
'''RRE09 Tag Detail Status (tag content and programming)'''
*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
{| class="wikitable"
! Code !! Meaning
|-
| <code>K</code> || OK
|-
| <code>A</code> || Axle error — tag axle count differs from axle pattern
|-
| <code>E</code> || EGC error — tag equipment group code differs from axle pattern
|-
| <code>L</code> || Length error — tag length differs from axle pattern by more than 15%
|-
| <code>O</code> || Placement error — tag applied on wrong side or wrong end
|-
| <code>D</code> || Unused tag read on equipment
|-
| <code>W</code> || Window violation — tag outside the AAR acceptable application window
|-
| <code>M</code> || Multiple errors
|-
| <code>?</code> || Other
|}


* *RRE03 (Owner Code)* — 4 chars, *space-padded* SCAC ("SP&nbsp;&nbsp;"). *Blank if not tagged.* Trim on read; distinguish blank-because-untagged from blank-because-suppressed by checking RRE08 = N.
<div style="border-left:4px solid #c77c1e; background:#fdf3e3; padding:0.6em 1em; margin:1em 0;">
* *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.
'''RRE09 = "M" triggers a required TDM segment'''
* *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
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".


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.
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.
</div>


h3. 4.1 Offset Table (Mode 1, 0-based, total 26 bytes)
=== The Duplicate-Sequence Rule ===


|| Ref || Off || Len || Type || Field ||
<div style="border-left:4px solid #c77c1e; background:#fdf3e3; padding:0.6em 1em; margin:1em 0;">
| EOT00 | 0 | 3 | AN | Segment ID = "EOT" |
'''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".
| 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
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.
|| Code || Meaning ||
</div>
| K | OK |
| H | Performance — low handshakes |
| W | Window violation — tag in wrong location |
| ? | Other |


{note}
=== Field Handling Notes ===
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.
* '''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.


h2. 5. EOC — End of Consist
== EOT — End of Train Device ==


Mandatory terminating segment. Always the last segment of the consist report.
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.


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


Total: 13 bytes.
Mode 1 (fixed width, no delimiters), 0-based offsets, total '''26 bytes'''.


{warning:title=Byte count semantics}
{| class="wikitable"
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:
! Ref !! Off !! Len !! Type !! Field
|-
| <code>EOT00</code> || <code>0</code> || <code>3</code> || AN || Segment ID = "EOT"
|-
| <code>EOT01</code> || <code>3</code> || <code>3</code> || N || Sequence Number of the carrying vehicle
|-
| <code>EOT02</code> || <code>6</code> || <code>1</code> || A || Equipment Group Code (from tag; "E" per EGC table)
|-
| <code>EOT03</code> || <code>7</code> || <code>4</code> || A || Owner Code (SCAC)
|-
| <code>EOT04</code> || <code>11</code> || <code>10</code> || N || Owner Equipment Number
|-
| <code>EOT05</code> || <code>21</code> || <code>2</code> || N || Handshakes Antenna 0
|-
| <code>EOT06</code> || <code>23</code> || <code>2</code> || N || Handshakes Antenna 1
|-
| <code>EOT07</code> || <code>25</code> || <code>1</code> || A || Tag Detail Status
|}


{code}
=== EOT07 — Tag Detail Status ===
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.
{| class="wikitable"
{warning}
! Code !! Meaning
|-
| <code>K</code> || OK
|-
| <code>H</code> || Performance — low handshakes
|-
| <code>W</code> || Window violation — tag in wrong location
|-
| <code>?</code> || Other
|}


Absence of EOC means a truncated transmission — quarantine, do not partially commit.
<div style="border-left:4px solid #2e5c8a; background:#eef3f9; padding:0.6em 1em; margin:1em 0;">
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.
</div>


h2. 6. Units of Measure
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.
 
== EOC — End of Consist ==
 
Mandatory terminating segment. Always the last segment of the consist report. Total '''13 bytes'''.
 
{| class="wikitable"
! Ref !! Off !! Len !! Type !! Field
|-
| <code>EOC00</code> || <code>0</code> || <code>3</code> || AN || Segment ID = "EOC"
|-
| <code>EOC01</code> || <code>3</code> || <code>10</code> || N || Total Byte Count
|}
 
<div style="border-left:4px solid #c77c1e; background:#fdf3e3; padding:0.6em 1em; margin:1em 0;">
'''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:
 
<pre>
observed = byte_length(bytes from start of AEM through end of
                      the segment immediately preceding EOC)
 
if observed != int(EOC01):
    quarantine(consist)
</pre>
 
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.
</div>
 
Absence of EOC means a truncated transmission. Quarantine the consist; do not partially commit.
 
== Units of Measure ==


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


|| Segment || Field || E (English) || M (Metric) ||
{| class="wikitable"
| AEM | Max / Min / Avg Speed | mph | kph |
! Segment !! Field !! E (English) !! M (Metric)
| AEM | Train Length | feet | decimeters |
|-
| RRE | Speed | mph | kph |
| AEM || Maximum / Minimum / Average Speed || miles per hour || kilometers per hour
|-
| AEM || Train Length || feet || decimeters
|-
| RRE || Speed || miles per hour || kilometers per hour
|}


{warning}
<div style="border-left:4px solid #c77c1e; background:#fdf3e3; padding:0.6em 1em; margin:1em 0;">
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.
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}
</div>


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


h2. 7. Parser Contract
== Parser Contract ==


h3. 7.1 Ordering and Looping
=== Ordering and Looping ===


Segments must be transmitted in the standard T-94 sequence. Relevant structure for the segments in scope:
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 ||
{| class="wikitable"
| AEM | 1 | 1 | Required | 1 |
! Segment !! Loop L1 !! Loop L2 !! Status !! Max Occurs
| DCL | 1 | 1 | Optional loop | 999 |
|-
| RRE | 2 | 1 | Optional — begins RRE loop | 999 |
| <code>AEM</code> || 1 || 1 || Required || 1
| TDM | 2 | 1 | Required after RRE01 / conditional | — |
|-
| EOT | 2 | 2 | Optional | 99 |
| <code>DCL</code> || 1 || 1 || Optional loop || 999
| RTD | 2 | 6 | Mandatory in V1.1 | 99 |
|-
| CEQ | 2 | 13 | Optional — begins CEQ loop inside RRE loop | 999 |
| <code>RRE</code> || 2 || 1 || Optional — begins RRE loop || 999
| MMR | 2 | 14 | Optional — begins MMR loop inside RRE loop | 999 |
|-
| EOC | 3 | 1 | Required | 1 |
| <code>TDM</code> || 2 || 1 || Required after RRE01 / conditional || —
|-
| <code>EOT</code> || 2 || 2 || Optional || 99
|-
| <code>RTD</code> || 2 || 6 || Mandatory for Class 1 in V1.1 || 99
|-
| <code>CEQ</code> || 2 || 13 || Optional — begins CEQ loop inside RRE loop || 999
|-
| <code>MMR</code> || 2 || 14 || Optional — begins MMR loop inside RRE loop || 999
|-
| <code>EOC</code> || 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.
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.


h3. 7.2 Recommended State Machine
=== Recommended State Machine ===


{code}
<pre>
EXPECT_AEM
EXPECT_AEM
   -> AEM             : latch units(AEM14), version(AEM08), consist key,
   -> AEM   : latch units(AEM14), version(AEM08), consist key,
                        site health(AEM23); start byte counter
              site health(AEM23); start byte counter
 
EXPECT_DCL_OR_RRE
EXPECT_DCL_OR_RRE
   -> DCL*             : accumulate site asset inventory
   -> DCL*   : accumulate site asset inventory
   -> RRE             : first RRE -> next segment MUST be TDM (capability decl.)
   -> RRE   : first RRE -> next segment MUST be TDM (capability decl.)
 
IN_VEHICLE
IN_VEHICLE
   -> RRE             : close prior vehicle, open new.
   -> RRE   : close prior vehicle, open new.
                        If RRE01 == previous RRE01 -> ID-mismatch pair, same vehicle
              if RRE01 == previous RRE01
   -> TDM             : cross-check detail (required when prior RRE09 == 'M')
                  -> ID-mismatch pair, SAME vehicle
   -> RTD             : raw tag hex, 0..99 per 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
   -> EOT | CEQ | XSC | THI | HAT | DYx | GEN | ALM | MMR | TST
                      : attach to current RRE01
              : attach to current RRE01
   -> EOC             : close vehicle, validate byte count, emit consist
   -> EOC   : close vehicle, validate byte count, emit consist
 
DONE
DONE
{code}
</pre>


h3. 7.3 Defensive Rules
=== Defensive Rules ===


# *Dispatch on segment ID, never on line position.* Any segment may be disabled at the site.
# '''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.
# '''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.
# '''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.
# '''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 9999 1, and zero is invalid sequence alone is not unique across a long feed.
# '''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/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.
# '''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.
# '''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.
# '''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
=== Validation Checklist per Consist ===


|| Check || Source || On failure ||
{| class="wikitable"
| Segment ID recognized | 3-char prefix | Log + skip segment, continue |
! Check !! Source !! On failure
| AEM present and first | State machine | Reject consist |
|-
| EOC present and last | State machine | Quarantine as truncated |
| Segment ID recognized || 3-char prefix || Log and skip segment, continue
| Byte count matches | EOC01 | Advisory, then reject once convention confirmed |
|-
| Format version major digit supported | AEM08 digit 1 | Route to quarantine for review |
| AEM present and first || State machine || Reject consist
| RRE count reconciles to AEM24 + AEM26 | Counts | Data-quality flag, do not reject |
|-
| Tagged counts total counts | AEM24/25, AEM26/27 | Data-quality flag |
| EOC present and last || State machine || Quarantine as truncated
| Site health nominal | AEM23 | Raise maintenance alert on fatal codes |
|-
| Every RRE01 in 1..AEM24+AEM26 | Sequence | Data-quality flag |
| Byte count matches || EOC01 || Advisory, then reject once convention confirmed
| Duplicate RRE01 also has RRE08 = M | Mismatch rule | Log unexpected duplicate |
|-
| 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
|}


h2. 8. Open Items
== Open Items ==


* Confirm per-vendor CR/LF and trailing-delimiter treatment in the EOC01 byte count before enforcing rejection.
* 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.
* Confirm whether source sites emit the RTD and TDM segments, or legacy output without them. This determines whether their absence is an error or an expected condition.
* Register the delimiter character actually used per site (space vs {{*}}) in site configuration rather than detecting it per file.
* 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.
 
== 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.


h2. 9. References
<div style="border-left:4px solid #2e5c8a; background:#eef3f9; padding:0.6em 1em; margin:1em 0;">
'''<big>This page is an implementation summary. The AAR MSRP is licensed, copyrighted material</big>'''
</div>


* 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.
== Examples ==
* 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).
[[Media:ConsistExamples.zip|Example Consists]]
* Appendix A §1.0 of S-9203A — Equipment Group Code values.


{note:title=Do not host the standard here}
[[Category:AEI]]
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.
[[Category:Standards]]
{note}
[[Category:Software Development]]

Latest revision as of 13:08, 30 July 2026


Implementation notes for parsing AAR MSRP Standard S-9203 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.

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.

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

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.

AEM — Consist Header

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

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

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.

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

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.

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 (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

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.

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.

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.

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.

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

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.

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.

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

Parser Contract

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.

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

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 from 9999 back to 1 and zero is invalid, so sequence alone is not unique across a long feed.
  6. 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.
  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.

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

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 RTD and TDM segments, or legacy output without them. This determines whether their absence is an error or an expected condition.
  • Register the delimiter character actually used per site (space vs asterisk) in site configuration rather than detecting it per file.

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

Examples

Example Consists