S9203 Site to Host Consist Report Decoder Reference: Difference between revisions
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*..." |
No edit summary |
||
| 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*. | 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. | |||
A T-94 file is a sequence of fixed-order, line-oriented segments. Each segment begins with a 3-character segment ID. | == 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. | * *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. | * *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. | * *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. | |||
Three output modes are user-selectable at the reader, | |||
|| Mode || Field widths || Delimiter || Parse strategy || | || Mode || Field widths || Delimiter || Parse strategy || | ||
| Line 23: | Line 18: | ||
| 3 | Variable | present | Split on delimiter, index by position | | | 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 {{*}} 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 | |||
{note} | {note} | ||
| Line 31: | Line 24: | ||
{note} | {note} | ||
== 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. | ||
=== Offset Table (Mode 1, 0-based, total 78 bytes) === | |||
|| Ref || Off || Len || Type || Field || | || Ref || Off || Len || Type || Field || | ||
| AEM00 | 0 | 3 | AN | Segment ID = "AEM" | | | AEM00 | 0 | 3 | AN | Segment ID = "AEM" | | ||
| Line 68: | Line 59: | ||
| AEM28 | 74 | 4 | N | Total Axle Count | | | AEM28 | 74 | 4 | N | Total Axle Count | | ||
=== Implied Decimals === | |||
{code} | {code} | ||
AEM06 Time Zone "##.#" decimal assumed -> "080" = 8.0 hrs west of GMT | AEM06 Time Zone "##.#" decimal assumed -> "080" = 8.0 hrs west of GMT | ||
| Line 80: | Line 70: | ||
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. | ||
=== Code Lookups === | |||
*AEM10 / AEM11 — Conversion Status (confidence)* | *AEM10 / AEM11 — Conversion Status (confidence)* | ||
|| Code || Meaning || | || Code || Meaning || | ||
| Line 160: | Line 149: | ||
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}. | 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} | {code} | ||
loco_read_rate = AEM25 / AEM24 (Locomotives Tagged / Locomotive Count) | loco_read_rate = AEM25 / AEM24 (Locomotives Tagged / Locomotive Count) | ||
| Line 168: | Line 156: | ||
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. | ||
== 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. | ||
=== Offset Table (Mode 1, 0-based, total 38 bytes) === | |||
|| Ref || Off || Len || Type || Field || | || Ref || Off || Len || Type || Field || | ||
| RRE00 | 0 | 3 | AN | Segment ID = "RRE" | | | RRE00 | 0 | 3 | AN | Segment ID = "RRE" | | ||
| Line 191: | Line 177: | ||
| RRE14 | 36 | 2 | N | Platform Count | | | 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. | *RRE02 — Equipment Group Code:* D = Locomotive, R = Railcar (includes former non-revenue rail), ? = Unknown. Derived from axle pattern when the vehicle is untagged. | ||
| Line 228: | Line 213: | ||
{warning} | {warning} | ||
=== The Duplicate-Sequence Rule === | |||
{warning} | {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. | *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. | ||
| Line 380: | Line 364: | ||
* 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. | * 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 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). | * 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. | * 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. | |||
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. | |||
Revision as of 16:14, 29 July 2026
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
- *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 9999 → 1, and zero is invalid — 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.
- *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.
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.