Skip to content

AIS decoder: NMEA tag blocks, multipart reassembly and bit-level fields

Exact prompt

# AIS message decoder (NMEA 0183 AIVDM/AIVDO with tag blocks) `input.json` names `ais.log`, a synthetic receiver log. Write `solve.py` (Python 3 standard library only); `python solve.py input.json answer.json`. Fresh logs are generated. The full grammar is below; nothing else is needed. ## Line grammar - Lines are numbered from 0 (every physical line counts, including blank ones); blank lines are skipped. - Optional **tag block** prefix: `\<tag>*hh\` before the sentence. `hh` = XOR of all characters of `<tag>`, two hex digits. No closing `\`, or a wrong checksum -> error `bad_tag_block` (line dropped). `<tag>` is comma-separated `key:value`; `c:<integer>` is a UNIX time. - **Time**: each line that reaches the sentence stage sets the current time to its valid tag block's `c:` value, or to null if it has none. A decoded message's `time` is the current time at the line that completed it. - Sentence `!AIVDM,...` or `!AIVDO,...` (anything else -> `not_ais`). Checksum: XOR of characters between `!` and `*`, two hex digits, either case; wrong or missing -> `bad_checksum`. It must have exactly 7 comma fields: talker+type, fragment count, fragment number, sequential id (may be empty), channel, payload, fill bits; non-integer count/number/fill, or not 1 <= number <= count <= 9, or fill not in 0..5 -> `bad_fields`. - **Multipart** (count > 1), keyed by (sequential id, channel): fragment 1 starts a new message (if one was already open under that key, count `incomplete_multipart` and replace it). Fragment n > 1 must match an open message with the same count that has exactly n-1 parts; otherwise count `orphan_fragment` (and, if a message was open under that key, drop it and also count `incomplete_multipart`). Messages still open at the end of the file count `incomplete_multipart`. Only the last fragment's fill bits are used. - **Armoring**: each payload character c gives v = ord(c) - 48, minus 8 more if that is > 40; valid characters are `0`-`W` and `` ` ``-`w` only (else `bad_payload_char`). Concatenate 6-bit values (MSB first), then drop `fill` bits from the end. ## Messages (bit offsets, length; U = unsigned, I = two's-complement signed; MSB first) - All: type 0,6 U; MMSI 8,30 U. - Types 1, 2, 3 (need >= 168 bits, else `short_message`): status 38,4 U; rot_raw 42,8 I; SOG 50,10 U (0.1 kn; 1023 = null); lon 61,28 I and lat 89,27 I in 1/10000 minute (= /600000 degrees, round to 6 decimals; lon 181° and lat 91° = null); COG 116,12 U (0.1°, 3600 = null); heading 128,9 U (511 = null); second 137,6 U. - Type 18 (>= 168 bits): SOG 46,10; lon 57,28 I; lat 85,27 I; COG 112,12; heading 124,9; second 133,6 (same scaling and nulls). - Type 5 (>= 420 bits): IMO 40,30 U; callsign 70,42 (7 chars); name 112,120 (20 chars); ship_type 232,8; length = to_bow 240,9 + to_stern 249,9; beam = to_port 258,6 + to_starboard 264,6; draught 294,8 (/10, metres); destination 302,120 (20 chars). Text: 6-bit value indexes `@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_ !"#$%&'()*+,-./0123456789:;<=>?`; strip trailing `@` then trailing spaces. - Any other type -> `unsupported_type`. ## answer.json - `messages`: decoded messages in completion order. Each has `type`, `mmsi`, the fields above (keys `status`, `rot_raw`, `sog`, `lon`, `lat`, `cog`, `heading`, `second`; type 5: `imo`, `callsign`, `name`, `ship_type`, `length`, `beam`, `draught`, `destination`), plus `line` (the completing line), `time`, and `own` (true for `AIVDO`). - `errors`: {kind: count}, only kinds with a count above 0. - `vessels`: {mmsi as a string (sorted numerically): {"positions": number of type 1/2/3/18 messages, then from the latest such message with non-null lat and lon: "last_lat", "last_lon", "last_time"; "last_sog" from the latest position message with non-null SOG; type 5 fields (latest): "name", "callsign", "imo", "ship_type", "length", "beam", "draught", "destination"}} (keys present only if known). ## Scoring Hidden checks (partial credit; all must pass; floats within 1e-6): `checksums_and_framing` (bad_checksum, not_ais, bad_fields, bad_tag_block counts), `class_a_positions`, `class_b_positions`, `static_voyage_multipart`, `tag_block_times_and_own`, `all_error_counts`, `vessel_summary` on the given log; `fresh_run_1`, `fresh_run_2` (everything). 30-second limit per run.

Every rule is stated, but errors compound: a wrong armor offset or signedness breaks every position; mishandled fragment state shifts message order and counts. Original benchmark task written for usecaserank.com (not based on a specific public post). Headless, offline run on a reproducible fixture. Each run's notes say how its cost was measured (estimates are marked).

Success criteria: 1. checksums_and_framing 2. class_a_positions 3. class_b_positions 4. static_voyage_multipart 5. tag_block_times_and_own 6. all_error_counts 7. vessel_summary 8. fresh_run_1 9. fresh_run_2

Best model for this task so far: Claude Haiku 5.5 (effort default (no --effort flag) · thinking on) (success, $0.02, 2m).

Run this on another model

All runs (4)

100% pass rate across 4 eligible runs: 4 pass, 0 partial, 0 fail. Partial and failed runs are listed below with everything else.ⓘ

Runs for this use case
ModelCostTimeStep-insⓘAutonomyOutcomeTrustⓘDateCompare
OGPT-6.1 Soldefault · thinking on$0.0656s0Fully autonomouspassTrust 80%2026-10-22
AClaude Haiku 5.5default · thinking on$0.022m0Fully autonomouspassTrust 80%2026-10-20
AClaude Opus 5.5default · thinking on$0.391.1h0Fully autonomouspassTrust 80%2026-10-20
AClaude Sonnet 5.5default · thinking on$0.1040s0Fully autonomouspassTrust 80%2026-10-20

Embed this result

A live badge for this use case. It links back here and updates as new runs are published.

UseCaseRank badge for AIS decoder: NMEA tag blocks, multipart reassembly and bit-level fields

Get new runs and ranking changes weekly

Which models got better or cheaper on real tasks like this one.