GEMMA DEVELOPER AGENTSAVED EVIDENCE
← 31B real rollouts · 480 (exact stack, 32 lanes, 13-Nov)
ROLLOUT 2 · M0r13n/pyais

M0r13n__pyais-137

Solved
455 stotal time
17tool calls
17model calls
6,605tokens generated
113,352prompt tokens read

Agent exceeded session timeout (7.5 min)

Turn by turn

Token shading: p ≥ 0.9 0.5 – 0.9 0.2 – 0.5 below 0.2. Hover a token for its id and probability.

System instructions 4,822 characters
You are a principal Python software engineer resolving a repository issue in `/workspace`.
Your sole objective is to produce a minimal, root-cause source patch that passes every hidden fail-to-pass and pass-to-pass regression test on the first grading run.

## Critical Environment & Grader Rules
1. **Air-Gapped Sandbox & Available CLI Tools**: No network access. Never run `pip`, `uv`, `conda`, or `git fetch`. Note: `rg` (`ripgrep`) is NOT installed in the container — always use `grep -rn --include="*.py"` for fast exact searches so you never waste a tool call on `command not found`.
2. **Protected Grading Paths**: Before running official verification, the grading harness **strips and reverts all changes** to `tests/`, `test/`, `testing/`, `test_*.py`, `*_test.py`, `conftest.py`, `pyproject.toml`, `setup.cfg`, `pytest.ini`, and `tox.ini`. Every part of your fix MUST reside in non-test production source files.
3. **Untracked File Trap (`git add -N .`)**: `submit_patch` stages ALL untracked files inside `/workspace`! Never leave reproduction or scratch files inside `/workspace` — run reproductions inline via `python3 - <<'PY'` or inside `/tmp/repro.py` in a single `run_command`.
4. **Stdout Truncation Rule**: `run_command` truncates stdout after the first 5,000 characters from the TOP. Always run pytest quietly with short tracebacks:
   `pytest path/to/test_file.py -k pattern -q --tb=short --no-header`
   If a test file is large, redirect output to `/tmp/pytest.log` and inspect `tail -n 80 /tmp/pytest.log` so failure tracebacks at the bottom are never cut off.
5. **Strict Pacing Discipline**: `get_status` and `submit_patch` are free and do NOT count toward `max_tool_calls`. Target **16-22 counted tool calls total**, and **begin your first `edit_file` change no later than tool call 15**. Reserve at least 4 calls for verification, `git status --short && git diff`, and submission.

## Execution Protocol

### Phase 1 — Pinpoint Code & Tests Immediately (1-2 tool calls)
- The user message already contains `Workspace Layout` — do NOT waste a turn running `ls` on top-level directories.
- Run a single compound `run_command` with `grep -rn --include="*.py"` to locate:
  (a) the exact symbols, error strings, or functions from the problem description,
  (b) **all parallel or sibling implementations** (for example sync + async variants, `Client` + `AsyncClient`, serializer + deserializer, multiple backends/dialects, base + subclass methods, `__init__.py` / `__all__` exports), and
  (c) the closest existing unit test files under `tests/`.

### Phase 2 — Deep Read & Spec Anchor (2-5 tool calls)
- Use `read_file` with generous `start_line` and `end_line` windows (180-300 lines per call so you read whole modules in fewer calls) on the target function/class, callers, type annotations, and neighboring tests.
- Before editing, verify every explicit and implicit requirement from the issue: exact parameter names, defaults, boundary/domain edge cases, exception types/messages, `None` vs empty collection vs falsy (`0`, `False`, `""`) semantics, mutability/copying, and return types.

### Phase 3 — Reproduce Bug in One Call (1 tool call)
- Run ONE concise command (`python3 - <<'PY' ... PY` or targeted `pytest ... -q --tb=short --no-header`) via `run_command` to observe the exact pre-fix failure. Do NOT spend multiple tool calls writing separate reproduction scripts.

### Phase 4 — Surgical Implementation Across All Parallel Paths (1-4 tool calls)
- Begin editing by tool call 15! Use `edit_file` with 4-6 lines of exact surrounding context copied from `read_file`.
- Update ALL parallel codepaths (sync/async, multiple classes/dialects, type hints) consistently so partial fixes do not fail sibling tests.
- **2-Strike Edit Rule**: If `edit_file` fails twice on the same block due to whitespace or escaping, do not retry `edit_file` a third time — apply an atomic Python transformation via `run_command` and immediately verify `python3 -m py_compile path/to/file.py`.

### Phase 5 — Edge-Case Verification, Diff Hygiene & Clean Exit (2-4 tool calls)
- Run a single compound `run_command` testing both your reproduction + boundary edge cases (`None`, empty input, leading/trailing delimiters, subdomain/prefix boundaries, subclass/async parity) AND the relevant existing `pytest` module(s) with `-q --tb=short --no-header`.
- Run `git status --short && git diff` via `run_command`. Confirm:
  - ZERO untracked (`??`) files in `/workspace` (`rm -f` any untracked workspace files immediately),
  - ZERO modifications to `tests/` or protected config files (`git checkout -- tests/` if touched),
  - Minimal, clean diff with valid imports and indentation.
- Call `submit_patch` once verification passes, then respond with a brief 2-sentence plain-text summary and make NO further tool calls.
Task prompt 3,690 characters
You are evaluating a software engineering task for repository M0r13n/pyais.

Problem Statement:
Determination of the communication status field
## Problem
It seems that the current method of detecting and distinguishing communication status fields is not entirely reliable. For example, the following example incorrectly indicates that it is a SOTDMA communication status, although it is an ITDMA.
```python
decoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51')
com = decoded.get_communication_state()
print(f"SOTDMA: {decoded.is_sotdma}, ITDMA: {decoded.is_itdma}")
```
According to the following code it is checked whether the 20th bit of the communications status is set or not. However, as the com state is only 19 bits long, a bit from the previous data fields is checked here and not one from the com state. In the case of a type 1, 2 or 3 message, it would be checked whether the RAIM flag is set. Since 1 is the default value, pyais returns in almost all cases that it is a SOTDMA com state.
```python
@property
def is_sotdma(self) -> bool:
    """The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits"""
    return self.radio <= self.MAX_COMM_STATE_VALUE

@property
def is_itdma(self) -> bool:
    """The radio status field has it's 20th bit (MSB) set to 1"""
    return self.radio > self.MAX_COMM_STATE_VALUE
```
## Solution
It is better to use the message type rather than the bit length to indicate the type of communication status. Table 46 of the M.1371-5 standard shows which type uses which com state. Type 9, 18 and 26 messages are edge cases, as they can include one of the two com state types. The existing implementation can be used here, as with these message types the bit before the actual com state determines its type.

## Task Budget (Session terminates when any budget is exhausted)
- Time allowance: 7.5 minutes
- Tool calls allowance: 30 calls
- Max loop iterations: 40 turns

## Execution Environment Rules
- Single command timeout: 90 seconds (commands exceeding this fail without ending the session)
- Command output limit: 5000 characters
- File view limit: 150 lines per read_file call
- File character limit: 10000 characters per read_file call
- Environment is offline (no network/PyPI access). All repository and test dependencies are ALREADY pre-installed. Do NOT attempt to run pip install or download packages.

## Instructions:
0. All source code is under `/workspace`. Do NOT search outside `/workspace` (e.g. `/usr/`, `/wheels/`, system site-packages). If imports fail, the issue is in the source code under `/workspace`, not in missing system packages.
1. Analyze the problem statement and any provided hints carefully to identify all requested script paths, CLI subcommands, or Python modules.
2. Inspect existing codebase conventions and test files before making edits.
3. Verify your implementation using targeted tests or inline assertions before submitting.
4. Call submit_patch only after your implementation is complete and verified.
5. As your final action, you must return a text-only response reporting your completion to terminate the session.
## Workspace Layout
The repository is located at `/workspace`. Here is the directory tree (up to 3 levels):
```
./CHANGELOG.txt
./CODE_OF_CONDUCT.md
./Dockerfile
./LICENSE
./Makefile
./README.md
./docs
./examples
./mypy.ini
./pyais
./pyais/__init__.py
./pyais/ais_types.py
./pyais/constants.py
./pyais/decode.py
./pyais/encode.py
./pyais/exceptions.py
./pyais/filter.py
./pyais/main.py
./pyais/messages.py
./pyais/py.typed
./pyais/stream.py
./pyais/tracker.py
./pyais/util.py
./pyproject.toml
./requirements.txt
./tests
```
Turn 1at 13 s187 tokens out3,064 prompt tokens
The user is reporting a bug in `pyais` regarding the communication status field detection.
Specifically, `is_sotdma` and `is_itdma` properties are incorrectly determined because they rely on the bit length of the `radio` field, which seems to be overlapping with other bits of the message in some cases.
The suggested solution is to use the message type to determine the communication status type, except for message types 9, 18, and 26 which can be either.

Let's start by locating the `is_sotdma` and `is_itdma` properties. They are likely in `pyais/messages.py` or a similar file.
TOOL CALL · run_command
{
  "command": "grep -rn --include=\"*.py\" \"def is_sotdma\" ."
}
Result of run_command 118 characters
{
  "status": "ok",
  "stdout": "./pyais/messages.py:827:    def is_sotdma(self) -> bool:\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 187 tokens · stopped: STOP
<|channel>thought The user is reporting a bug in `pyais` regarding the communication status field detection. Specifically, `is_sotdma` and `is_itdma` properties are incorrectly determined because they rely on the bit length of the `radio` field, which seems to be overlapping with other bits of the message in some cases. The suggested solution is to use the message type to determine the communication status type, except for message types 9, 18, and 26 which can be either. Let's start by locating the `is_sotdma` and `is_itdma` properties. They are likely in `pyais/messages.py` or a similar file.<channel|><|tool_call>call:run_command{command:<|"|>grep -rn --include="*.py" "def is_sotdma" .<|"|>}<tool_call|><|tool_response>
Turn 2at 17 s61 tokens out3,316 prompt tokens
Okay, the properties are in `pyais/messages.py`. Let's examine the code around there.
TOOL CALL · read_file
{
  "end_line": 840,
  "filepath": "pyais/messages.py",
  "start_line": 820
}
Result of read_file 944 characters
{
  "status": "ok",
  "filepath": "pyais/messages.py",
  "content": "            result.update(get_sotdma_comm_state(self.communication_state_raw))\n        else:\n            result.update(get_itdma_comm_state(self.communication_state_raw))\n\n        return result\n\n    @property\n    def is_sotdma(self) -> bool:\n        \"\"\"The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits\"\"\"\n        return self.radio <= self.MAX_COMM_STATE_VALUE\n\n    @property\n    def is_itdma(self) -> bool:\n        \"\"\"The radio status field has it's 20th bit (MSB) set to 1\"\"\"\n        return self.radio > self.MAX_COMM_STATE_VALUE\n\n    @property\n    def communication_state_raw(self) -> int:\n        \"\"\"Get the raw radio status except 20th bit - if present\"\"\"\n        try:\n            return self.radio & self.MAX_COMM_STATE_VALUE",
  "start_line": 820,
  "end_line": 840,
  "total_lines": 1736,
  "is_truncated": false
}
Generated tokens 61 tokens · stopped: STOP
Okay, the properties are in `pyais/messages.py`. Let's examine the code around there. <channel|><|tool_call>call:read_file{filepath:<|"|>pyais/messages.py<|"|>,start_line:820,end_line:840}<tool_call|><|tool_response>
Turn 3at 39 s309 tokens out3,717 prompt tokens
I need to see where `MAX_COMM_STATE_VALUE` is defined and what `self.radio` is.
Also, I need to see where `self.radio` is set.

The problem statement says:
"It is better to use the message type rather than the bit length to indicate the type of communication status."
And mentions that message types 9, 18, and 26 are edge cases.

Let's find `MAX_COMM_STATE_VALUE` first. It might be in `pyais/constants.py` or within the class in `pyais/messages.py`.
Let's look at the beginning of `pyais/messages.py` or check `constants.py`.
Actually, let's look at the class definition including `MAX_COMM_STATE_VALUE` if it's a class attribute.
The properties are in a class. Let's see which class.
I'll read more of `pyais/messages.py`.
Wait, `is_sotdma` is a property of some class. I need to know which one.
Looking at the context, it's likely `AisMessage` or a base class for messages.

Let's find where `MAX_COMM_STATE_VALUE` is defined.
TOOL CALL · run_command
{
  "command": "grep -rn \"MAX_COMM_STATE_VALUE\" ."
}
Result of run_command 362 characters
{
  "status": "ok",
  "stdout": "./pyais/messages.py:802:    MAX_COMM_STATE_VALUE = 0x7ffff\n./pyais/messages.py:829:        return self.radio <= self.MAX_COMM_STATE_VALUE\n./pyais/messages.py:834:        return self.radio > self.MAX_COMM_STATE_VALUE\n./pyais/messages.py:840:            return self.radio & self.MAX_COMM_STATE_VALUE\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 309 tokens · stopped: STOP
I need to see where `MAX_COMM_STATE_VALUE` is defined and what `self.radio` is. Also, I need to see where `self.radio` is set. The problem statement says: "It is better to use the message type rather than the bit length to indicate the type of communication status." And mentions that message types 9, 18, and 26 are edge cases. Let's find `MAX_COMM_STATE_VALUE` first. It might be in `pyais/constants.py` or within the class in `pyais/messages.py`. Let's look at the beginning of `pyais/messages.py` or check `constants.py`. Actually, let's look at the class definition including `MAX_COMM_STATE_VALUE` if it's a class attribute. The properties are in a class. Let's see which class. I'll read more of `pyais/messages.py`. Wait, `is_sotdma` is a property of some class. I need to know which one. Looking at the context, it's likely `AisMessage` or a base class for messages. Let's find where `MAX_COMM_STATE_VALUE` is defined. <channel|><|tool_call>call:run_command{command:<|"|>grep -rn "MAX_COMM_STATE_VALUE" .<|"|>}<tool_call|><|tool_response>
Turn 4at 43 s59 tokens out4,178 prompt tokens
It's defined in `pyais/messages.py`.

Let's identify the class.
TOOL CALL · read_file
{
  "end_line": 820,
  "filepath": "pyais/messages.py",
  "start_line": 800
}
Result of read_file 910 characters
{
  "status": "ok",
  "filepath": "pyais/messages.py",
  "content": "    radio: int  # Type hint to make mypy happy\n\n    MAX_COMM_STATE_VALUE = 0x7ffff\n\n    def get_communication_state(self) -> Dict[str, typing.Optional[int]]:\n        \"\"\"Returns information used by the slot allocation algorithm as a dict.\"\"\"\n        result: Dict[str, typing.Optional[int]] = {\n            'received_stations': None,\n            'slot_number': None,\n            'utc_hour': None,\n            'utc_minute': None,\n            'slot_offset': None,\n            'slot_timeout': None,\n            'sync_state': None,\n            'keep_flag': None,\n            'slot_increment': None,\n            'num_slots': None,\n        }\n\n        if self.is_sotdma:\n            result.update(get_sotdma_comm_state(self.communication_state_raw))",
  "start_line": 800,
  "end_line": 820,
  "total_lines": 1736,
  "is_truncated": false
}
Generated tokens 59 tokens · stopped: STOP
It's defined in `pyais/messages.py`. Let's identify the class. <channel|><|tool_call>call:read_file{end_line:820,filepath:<|"|>pyais/messages.py<|"|>,start_line:800}<tool_call|><|tool_response>
Turn 5at 45 s55 tokens out4,536 prompt tokens
I still don't see the class name. Let's go further up.
TOOL CALL · read_file
{
  "end_line": 800,
  "filepath": "pyais/messages.py",
  "start_line": 780
}
Result of read_file 783 characters
{
  "status": "ok",
  "filepath": "pyais/messages.py",
  "content": "    return math.copysign(round((turn / 4.733) ** 2), turn)\n\n\ndef from_turn(turn: typing.Optional[typing.Union[int, float, TurnRate]]) -> int:\n    if not turn:\n        return 0\n    elif abs(turn) == 127 or abs(turn) == 128:\n        return int(turn)\n\n    return int(math.copysign(round(4.733 * math.sqrt(abs(turn))), turn))\n\n\nclass CommunicationStateMixin:\n    \"\"\"\n    Mixin class to access Communication State values by applicable messages.\n\n    You may refer to 3.3.7.2.1 of:\n    https://www.itu.int/dms_pubrec/itu-r/rec/m/R-REC-M.1371-1-200108-S!!PDF-E.pdf\n    \"\"\"\n\n    radio: int  # Type hint to make mypy happy",
  "start_line": 780,
  "end_line": 800,
  "total_lines": 1736,
  "is_truncated": false
}
Generated tokens 55 tokens · stopped: STOP
I still don't see the class name. Let's go further up. <channel|><|tool_call>call:read_file{end_line:800,filepath:<|"|>pyais/messages.py<|"|>,start_line:780}<tool_call|><|tool_response>
Turn 6at 59 s247 tokens out4,914 prompt tokens
The class is `CommunicationStateMixin`.

Now let's reproduce the bug with the provided example.

```python
import pyais
decoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51')
com = decoded.get_communication_state()
print(f"SOTDMA: {decoded.is_sotdma}, ITDMA: {decoded.is_itdma}")
```
TOOL CALL · run_command
{
  "command": "python3 - <<'PY'\nimport pyais\ntry:\n    decoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51')\n    print(f\"SOTDMA: {decoded.is_sotdma}, ITDMA: {decoded.is_itdma}\")\nexcept Exception as e:\n    print(f\"Error: {e}\")\nPY\n"
}
Result of run_command 88 characters
{
  "status": "ok",
  "stdout": "SOTDMA: True, ITDMA: False\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 247 tokens · stopped: STOP
The class is `CommunicationStateMixin`. Now let's reproduce the bug with the provided example. ```python import pyais decoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51') com = decoded.get_communication_state() print(f"SOTDMA: {decoded.is_sotdma}, ITDMA: {decoded.is_itdma}") ``` <channel|><|tool_call>call:run_command{command:<|"|>python3 - <<'PY' import pyais try: decoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51') print(f"SOTDMA: {decoded.is_sotdma}, ITDMA: {decoded.is_itdma}") except Exception as e: print(f"Error: {e}") PY <|"|>}<tool_call|><|tool_response>
Turn 7at 80 s367 tokens out5,211 prompt tokens
The problem statement says this should be ITDMA.
"the following example incorrectly indicates that it is a SOTDMA communication status, although it is an ITDMA."

So the current output `SOTDMA: True, ITDMA: False` is indeed incorrect. It should be `SOTDMA: False, ITDMA: True`.

Now let's see why the proposed solution says to use the message type.
"It is better to use the message type rather than the bit length to indicate the type of communication status. Table 46 of the M.1371-5 standard shows which type uses which com state. Type 9, 18 and 26 messages are edge cases, as they can include one of the two com state types. The existing implementation can be used here, as with these message types the bit before the actual com state determines its type."

I need to know where the message type is stored in the decoded object.
Usually, AIS messages have a `msgtype` or `type` attribute. Let's check the `decoded` object in the repro.
TOOL CALL · run_command
{
  "command": "python3 - <<'PY'\nimport pyais\ndecoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51')\nprint(f\"Type: {getattr(decoded, 'msgtype', 'N/A')}, Type2: {getattr(decoded, 'type', 'N/A')}\")\nprint(f\"Radio: {getattr(decoded, 'radio', 'N/A')}\")\nPY\n"
}
Result of run_command 97 characters
{
  "status": "ok",
  "stdout": "Type: N/A, Type2: N/A\nRadio: 21075\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 367 tokens · stopped: STOP
The problem statement says this should be ITDMA. "the following example incorrectly indicates that it is a SOTDMA communication status, although it is an ITDMA." So the current output `SOTDMA: True, ITDMA: False` is indeed incorrect. It should be `SOTDMA: False, ITDMA: True`. Now let's see why the proposed solution says to use the message type. "It is better to use the message type rather than the bit length to indicate the type of communication status. Table 46 of the M.1371-5 standard shows which type uses which com state. Type 9, 18 and 26 messages are edge cases, as they can include one of the two com state types. The existing implementation can be used here, as with these message types the bit before the actual com state determines its type." I need to know where the message type is stored in the decoded object. Usually, AIS messages have a `msgtype` or `type` attribute. Let's check the `decoded` object in the repro. <channel|><|tool_call>call:run_command{command:<|"|>python3 - <<'PY' import pyais decoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51') print(f"Type: {getattr(decoded, 'msgtype', 'N/A')}, Type2: {getattr(decoded, 'type', 'N/A')}") print(f"Radio: {getattr(decoded, 'radio', 'N/A')}") PY <|"|>}<tool_call|><|tool_response>
Turn 8at 84 s113 tokens out5,640 prompt tokens
Hmm, it seems I don't have `msgtype` or `type`. Let's see what attributes the `decoded` object has.
TOOL CALL · run_command
{
  "command": "python3 - <<'PY'\nimport pyais\ndecoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51')\nprint(dir(decoded))\nPY\n"
}
Result of run_command 914 characters
{
  "status": "ok",
  "stdout": "['MAX_COMM_STATE_VALUE', '_Payload__force_type', '__abstractmethods__', '__annotations__', '__attrs_attrs__', '__attrs_own_setattr__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '__weakref__', '_abc_impl', 'accuracy', 'asdict', 'communication_state_raw', 'course', 'create', 'encode', 'fields', 'from_bitarray', 'get_communication_state', 'heading', 'is_itdma', 'is_sotdma', 'lat', 'lon', 'maneuver', 'mmsi', 'msg_type', 'radio', 'raim', 'repeat', 'second', 'spare_1', 'speed', 'status', 'to_bitarray', 'to_json', 'turn']\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 113 tokens · stopped: STOP
Hmm, it seems I don't have `msgtype` or `type`. Let's see what attributes the `decoded` object has. <channel|><|tool_call>call:run_command{command:<|"|>python3 - <<'PY' import pyais decoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51') print(dir(decoded)) PY <|"|>}<tool_call|><|tool_response>
Turn 9at 89 s115 tokens out6,082 prompt tokens
The attribute is `msg_type`.
Let's check the `msg_type` for the reproducer:
TOOL CALL · run_command
{
  "command": "python3 - <<'PY'\nimport pyais\ndecoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51')\nprint(f\"Msg Type: {decoded.msg_type}\")\nPY\n"
}
Result of run_command 73 characters
{
  "status": "ok",
  "stdout": "Msg Type: 3\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 115 tokens · stopped: STOP
The attribute is `msg_type`. Let's check the `msg_type` for the reproducer: <channel|><|tool_call>call:run_command{command:<|"|>python3 - <<'PY' import pyais decoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51') print(f"Msg Type: {decoded.msg_type}") PY <|"|>}<tool_call|><|tool_response>
Turn 10at 127 s581 tokens out6,242 prompt tokens
The msg type is 3.
The problem statement says: "In the case of a type 1, 2 or 3 message, it would be checked whether the RAIM flag is set. Since 1 is the default value, pyais returns in almost all cases that it is a SOTDMA com state."

Looking at the code for `is_sotdma`:
```python
    @property
    def is_sotdma(self) -> bool:
        """The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits"""
        return self.radio <= self.MAX_COMM_STATE_VALUE
```
And `MAX_COMM_STATE_VALUE = 0x7ffff`.
`0x7ffff` is $2^{19} - 1$.
So if `self.radio` has the 20th bit set (i.e., $\ge 2^{19}$), `is_sotdma` is `False` and `is_itdma` is `True`.

For the example, `radio` is 21075. $21075 \le 524287 (0x7ffff)$. So `is_sotdma` is `True`.

Wait, the problem says "the following example incorrectly indicates that it is a SOTDMA communication status, although it is an ITDMA."
For `decoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51')`, `msg_type` is 3.

The solution says: "It is better to use the message type rather than the bit length to indicate the type of communication status."

I need to know which message types use SOTDMA and which use ITDMA.
The problem says: "Type 9, 18 and 26 messages are edge cases, as they can include one of the two com state types."

According to AIS standard (SOTDMA is usually used in most messages, ITDMA in others), let's see if we can find this info in the codebase or if I have to guess from the "Type 9, 18 and 26" comment.

Actually, maybe there's a list of types in `pyais/messages.py` or `pyais/constants.py`.
TOOL CALL · run_command
{
  "command": "grep -rn \"msg_type\" pyais/messages.py | head -n 20"
}
Result of run_command 1,281 characters
{
  "status": "ok",
  "stdout": "547:        MessageType18(msg_type=18, ...)\n853:    msg_type = bit_field(6, int, default=1, signed=False)\n877:    msg_type = bit_field(6, int, default=2)\n885:    msg_type = bit_field(6, int, default=3)\n894:    msg_type = bit_field(6, int, default=4, signed=False)\n919:    msg_type = bit_field(6, int, default=5, signed=False)\n948:    msg_type = bit_field(6, int, default=6)\n966:    msg_type = bit_field(6, int, default=7, signed=False)\n986:    msg_type = bit_field(6, int, default=8, signed=False)\n1001:    msg_type = bit_field(6, int, default=9, signed=False)\n1028:    msg_type = bit_field(6, int, default=10, signed=False)\n1041:    msg_type = bit_field(6, int, default=11, signed=False)\n1050:    msg_type = bit_field(6, int, default=12, signed=False)\n1064:    msg_type = bit_field(6, int, default=13, signed=False)\n1073:    msg_type = bit_field(6, int, default=14, signed=False)\n1086:    msg_type = bit_field(6, int, default=15, signed=False)\n1109:    msg_type = bit_field(6, int, default=16, signed=False)\n1129:    msg_type = bit_field(6, int, default=17, signed=False)\n1146:    msg_type = bit_field(6, int, default=18, signed=False)\n1175:    msg_type = bit_field(6, int, default=19, signed=False)\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 581 tokens · stopped: STOP
The msg type is 3. The problem statement says: "In the case of a type 1, 2 or 3 message, it would be checked whether the RAIM flag is set. Since 1 is the default value, pyais returns in almost all cases that it is a SOTDMA com state." Looking at the code for `is_sotdma`: ```python @property def is_sotdma(self) -> bool: """The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits""" return self.radio <= self.MAX_COMM_STATE_VALUE ``` And `MAX_COMM_STATE_VALUE = 0x7ffff`. `0x7ffff` is $2^{19} - 1$. So if `self.radio` has the 20th bit set (i.e., $\ge 2^{19}$), `is_sotdma` is `False` and `is_itdma` is `True`. For the example, `radio` is 21075. $21075 \le 524287 (0x7ffff)$. So `is_sotdma` is `True`. Wait, the problem says "the following example incorrectly indicates that it is a SOTDMA communication status, although it is an ITDMA." For `decoded = pyais.decode('!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51')`, `msg_type` is 3. The solution says: "It is better to use the message type rather than the bit length to indicate the type of communication status." I need to know which message types use SOTDMA and which use ITDMA. The problem says: "Type 9, 18 and 26 messages are edge cases, as they can include one of the two com state types." According to AIS standard (SOTDMA is usually used in most messages, ITDMA in others), let's see if we can find this info in the codebase or if I have to guess from the "Type 9, 18 and 26" comment. Actually, maybe there's a list of types in `pyais/messages.py` or `pyais/constants.py`.<channel|><|tool_call>call:run_command{command:<|"|>grep -rn "msg_type" pyais/messages.py | head -n 20<|"|>}<tool_call|><|tool_response>
Turn 11at 147 s337 tokens out7,384 prompt tokens
I need to find which message types are SOTDMA and which are ITDMA.
The problem description says "Table 46 of the M.1371-5 standard shows which type uses which com state."
Unfortunately, I don't have the standard. But I can look for clues in the problem description.
"Type 9, 18 and 26 messages are edge cases, as they can include one of the two com state types."

What about other types?
Actually, wait.-
"In the case of a type 1, 2 or 3 message, it would be checked whether the RAIM flag is set. Since 1 is the default value, pyais returns in almost cases that it is a SOTDMA com state."

Wait, if type 1, 2, 3 messages use a different mechanism for communication state determination, maybe I should check what that is.

Actually, let's read the problem statement again carefully.
"It is better to use the message type rather than the bit length to indicate the type of communication status. Table 46 of the M.1371-5 standard shows which type uses which com state."

If I don't have Table 46, how am I supposed to know?
Wait, maybe the code already has some information about this?
Let's see if there are any other mentions of "SOTDMA" or "ITDMA" in the code.
TOOL CALL · run_command
{
  "command": "grep -rn \"SOTDMA\" ."
}
Result of run_command 1,438 characters
{
  "status": "ok",
  "stdout": "./pyais/messages.py:850:    AIS Vessel position report using SOTDMA (Self-Organizing Time Division Multiple Access)\n./pyais/messages.py:874:    AIS Vessel position report using SOTDMA (Self-Organizing Time Division Multiple Access)\n./pyais/messages.py:891:    AIS Vessel position report using SOTDMA (Self-Organizing Time Division Multiple Access)\n./pyais/util.py:329:    The SOTDMA communication state is structured as follows:\n./README.md:324:Such details include information used by the slot allocation algorithm (either SOTDMA or ITDMA) including their synchronization state.\n./CHANGELOG.txt:155:  * provided functions to access SOTDMA/ITDMA communication state information\n./docs/messages.rst:86:Such details include information used by the slot allocation algorithm (either SOTDMA or ITDMA) including their synchronization state.\r\n./docs/messages.rst:90:The following messages have the SOTDMA communication state:\r\n./docs/messages.rst:108:- `.is_sotdma()`: Returns True when using the SOTDMA algorithm\r\n./docs/messages.rst:144:    AIS Vessel position report using SOTDMA (Self-Organizing Time Division Multiple Access)\r\n./docs/messages.rst:215:    AIS Vessel position report using SOTDMA (Self-Organizing Time Division Multiple Access)\r\n./docs/messages.rst:357:    AIS Vessel position report using SOTDMA (Self-Organizing Time Division Multiple Access)\r\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 337 tokens · stopped: STOP
I need to find which message types are SOTDMA and which are ITDMA. The problem description says "Table 46 of the M.1371-5 standard shows which type uses which com state." Unfortunately, I don't have the standard. But I can look for clues in the problem description. "Type 9, 18 and 26 messages are edge cases, as they can include one of the two com state types." What about other types? Actually, wait.- "In the case of a type 1, 2 or 3 message, it would be checked whether the RAIM flag is set. Since 1 is the default value, pyais returns in almost cases that it is a SOTDMA com state." Wait, if type 1, 2, 3 messages use a different mechanism for communication state determination, maybe I should check what that is. Actually, let's read the problem statement again carefully. "It is better to use the message type rather than the bit length to indicate the type of communication status. Table 46 of the M.1371-5 standard shows which type uses which com state." If I don't have Table 46, how am I supposed to know? Wait, maybe the code already has some information about this? Let's see if there are any other mentions of "SOTDMA" or "ITDMA" in the code. <channel|><|tool_call>call:run_command{command:<|"|>grep -rn "SOTDMA" .<|"|>}<tool_call|><|tool_response>
Turn 12at 150 s56 tokens out8,133 prompt tokens
Wait, the `docs/messages.rst` might have the list of messages!
TOOL CALL · run_command
{
  "command": "grep -A 20 \"The following messages have the SOTDMA communication state:\" docs/messages.rst"
}
Result of run_command 750 characters
{
  "status": "ok",
  "stdout": "The following messages have the SOTDMA communication state:\r\n\r\n- Type 1\r\n- Type 2\r\n- Type 4\r\n- Type 9\r\n- Type 11\r\n- Type 18\r\n\r\nThe following messages have the ITDMA communication state:\r\n\r\n- Type 3\r\n- Type 18\r\n\r\nThese messages have in common that they have the `.radio` attribute. This attribute holds the **raw value** of the last 19 data-bits.\r\n\r\nFurther details can be retrieved by using one of the following methods:\r\n\r\n- `.is_sotdma()`: Returns True when using the SOTDMA algorithm\r\n- `.is_itdma()`: Returns True when using the ITDMA algorithm\r\n- `get_communication_state()`: information used by the slot allocation algorithm as a dictionary\r\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 56 tokens · stopped: STOP
Wait, the `docs/messages.rst` might have the list of messages! <channel|><|tool_call>call:run_command{command:<|"|>grep -A 20 "The following messages have the SOTDMA communication state:" docs/messages.rst<|"|>}<tool_call|><|tool_response>
Turn 13at 164 s211 tokens out8,462 prompt tokens
Okay! Now I have the list!
SOTDMA: 1, 2, 4, 9, 11, 18
ITDMA: 3, 18 (Wait, the docs say 18 is both)

Wait, the documentation says:
SOTDMA: 1, 2, 4, 9, 11, 18
ITDMA: 3, 18

But the problem statement says: "Type 9, 18 and 26 messages are edge cases, as they can include one of the two com state types."
My list from `docs/messages.rst` has 9 and 18. Where is 26?

Let's look at the `docs/messages.rst` again, or maybe the file is larger.
TOOL CALL · run_command
{
  "command": "grep -C 5 \"Type 26\" docs/messages.rst"
}
Result of run_command 127 characters
{
  "status": "error",
  "error_type": "CommandError",
  "error_message": "",
  "details": {
    "stdout": "",
    "stderr": "",
    "exit_code": 1
  }
}
Generated tokens 211 tokens · stopped: STOP
Okay! Now I have the list! SOTDMA: 1, 2, 4, 9, 11, 18 ITDMA: 3, 18 (Wait, the docs say 18 is both) Wait, the documentation says: SOTDMA: 1, 2, 4, 9, 11, 18 ITDMA: 3, 18 But the problem statement says: "Type 9, 18 and 26 messages are edge cases, as they can include one of the two com state types." My list from `docs/messages.rst` has 9 and 18. Where is 26? Let's look at the `docs/messages.rst` again, or maybe the file is larger. <channel|><|tool_call>call:run_command{command:<|"|>grep -C 5 "Type 26" docs/messages.rst<|"|>}<tool_call|><|tool_response>
Turn 14at 187 s366 tokens out8,728 prompt tokens
Maybe I should just search for all mentions of "Type" in `docs/messages.rst` around communication state.

Actually, let's look at `pyais/messages.py` to see what message types exist.

The problem states: "It is better to use the message type rather than the bit length to indicate the type of communication status."

If `msg_type` is in {1, 2, 4, 11}, it's SOTDMA.
If `msg_type` is 3, it's ITDMA.
If `msg_type` is in {9, 18, 26}, we use the existing bit length check.

Wait, what about other types?
The docs said:
SOTDMA: 1, 2, 4, 9, 11, 18
ITDMA: 3, 18

Let's check if there are other types with `.radio`.
The docs say: "These messages have in common that they have the `.radio` attribute."
And then lists 1, 2, 4, 9, 11, 18 for SOTDMA and 3, 18 for ITDMA.

Wait, the problem says "Type 9, 18 and 26".
Maybe the docs are incomplete.

Let's look at `pyais/messages.py` again. I see `MessageType18` on line 547.

Let's see who inherits from `CommunicationStateMixin`.
TOOL CALL · run_command
{
  "command": "grep -rn \"CommunicationStateMixin\" pyais/messages.py"
}
Result of run_command 658 characters
{
  "status": "ok",
  "stdout": "792:class CommunicationStateMixin:\n848:class MessageType1(Payload, CommunicationStateMixin):\n889:class MessageType4(Payload, CommunicationStateMixin):\n996:class MessageType9(Payload, CommunicationStateMixin):\n1141:class MessageType18(Payload, CommunicationStateMixin):\n1545:class MessageType26AddressedStructured(Payload, CommunicationStateMixin):\n1560:class MessageType26BroadcastStructured(Payload, CommunicationStateMixin):\n1574:class MessageType26AddressedUnstructured(Payload, CommunicationStateMixin):\n1589:class MessageType26BroadcastUnstructured(Payload, CommunicationStateMixin):\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 366 tokens · stopped: STOP
Maybe I should just search for all mentions of "Type" in `docs/messages.rst` around communication state. Actually, let's look at `pyais/messages.py` to see what message types exist. The problem states: "It is better to use the message type rather than the bit length to indicate the type of communication status." If `msg_type` is in {1, 2, 4, 11}, it's SOTDMA. If `msg_type` is 3, it's ITDMA. If `msg_type` is in {9, 18, 26}, we use the existing bit length check. Wait, what about other types? The docs said: SOTDMA: 1, 2, 4, 9, 11, 18 ITDMA: 3, 18 Let's check if there are other types with `.radio`. The docs say: "These messages have in common that they have the `.radio` attribute." And then lists 1, 2, 4, 9, 11, 18 for SOTDMA and 3, 18 for ITDMA. Wait, the problem says "Type 9, 18 and 26". Maybe the docs are incomplete. Let's look at `pyais/messages.py` again. I see `MessageType18` on line 547. Let's see who inherits from `CommunicationStateMixin`. <channel|><|tool_call>call:run_command{command:<|"|>grep -rn "CommunicationStateMixin" pyais/messages.py<|"|>}<tool_call|><|tool_response>
Turn 15at 198 s131 tokens out9,292 prompt tokens
Okay, the classes that use `CommunicationStateMixin` are:
`MessageType1`
`MessageType4` (Wait, where is MessageType2?)
`MessageType9`
`MessageType18`
`MessageType26AddressedStructured`
`MessageType26BroadcastStructured`
`MessageType26AddressedUnstructured`
`MessageType26BroadcastUnstructured`

Wait, let me check for `MessageType2` and `MessageType3` and `MessageType11`.
TOOL CALL · run_command
{
  "command": "grep -rn \"class MessageType\" pyais/messages.py"
}
Result of run_command 1,854 characters
{
  "status": "ok",
  "stdout": "848:class MessageType1(Payload, CommunicationStateMixin):\n872:class MessageType2(MessageType1):\n880:class MessageType3(MessageType1):\n889:class MessageType4(Payload, CommunicationStateMixin):\n914:class MessageType5(Payload):\n943:class MessageType6(Payload):\n961:class MessageType7(Payload):\n981:class MessageType8(Payload):\n996:class MessageType9(Payload, CommunicationStateMixin):\n1023:class MessageType10(Payload):\n1036:class MessageType11(MessageType4):\n1045:class MessageType12(Payload):\n1060:class MessageType13(MessageType7):\n1068:class MessageType14(Payload):\n1081:class MessageType15(Payload):\n1104:class MessageType16(Payload):\n1124:class MessageType17(Payload):\n1141:class MessageType18(Payload, CommunicationStateMixin):\n1170:class MessageType19(Payload):\n1203:class MessageType20(Payload):\n1235:class MessageType21(Payload):\n1268:class MessageType22Addressed(Payload):\n1299:class MessageType22Broadcast(Payload):\n1330:class MessageType22(Payload):\n1357:class MessageType23(Payload):\n1386:class MessageType24PartA(Payload):\n1397:class MessageType24PartB(Payload):\n1417:class MessageType24(Payload):\n1450:class MessageType25AddressedStructured(Payload):\n1464:class MessageType25BroadcastStructured(Payload):\n1477:class MessageType25AddressedUnstructured(Payload):\n1490:class MessageType25BroadcastUnstructured(Payload):\n1501:class MessageType25(Payload):\n1545:class MessageType26AddressedStructured(Payload, CommunicationStateMixin):\n1560:class MessageType26BroadcastStructured(Payload, CommunicationStateMixin):\n1574:class MessageType26AddressedUnstructured(Payload, CommunicationStateMixin):\n1589:class MessageType26BroadcastUnstructured(Payload, CommunicationStateMixin):\n1601:class MessageType26(Payload):\n1645:class MessageType27(Payload):\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 131 tokens · stopped: STOP
Okay, the classes that use `CommunicationStateMixin` are: `MessageType1` `MessageType4` (Wait, where is MessageType2?) `MessageType9` `MessageType18` `MessageType26AddressedStructured` `MessageType26BroadcastStructured` `MessageType26AddressedUnstructured` `MessageType26BroadcastUnstructured` Wait, let me check for `MessageType2` and `MessageType3` and `MessageType11`. <channel|><|tool_call>call:run_command{command:<|"|>grep -rn "class MessageType" pyais/messages.py<|"|>}<tool_call|><|tool_response>
Turn 16at 356 s2,824 tokens out10,056 prompt tokens
Okay, so:
`MessageType1` inherits `CommunicationStateMixin`.
`MessageType2` inherits `MessageType1` $\rightarrow$ `CommunicationStateMixin`.
`MessageType3` inherits `MessageType1` $\rightarrow$ `CommunicationStateMixin`.
`MessageType4` inherits `CommunicationStateMixin`.
`MessageType11` inherits `MessageType4` $\rightarrow$ `CommunicationStateMixin`.
`MessageType9` inherits `CommunicationStateMixin`.
`MessageType18` inherits `CommunicationStateMixin`.
`MessageType26...` inherit `CommunicationStateMixin`.

Now, based on the problem description:
SOTDMA: 1, 2, 4, 11
ITDMA: 3
Edge cases (both): 9, 18, 26

Wait, let me re-read.
"Table 46 of the M.1371-5 standard shows which type uses which com state. Type 9, 18 and 26 messages are edge cases, as they can include one of the two com state types. The existing implementation can be used here, as with these message types the bit before the actual com state determines its type."

So for types 9, 18, 26, the existing `is_sotdma` implementation (which checks `self.radio <= self.MAX_COMM_STATE_VALUE`) is correct.

For other types, we should use the message type.
Types using `CommunicationStateMixin` are: 1, 2, 3, 4, 9, 11, 18, 26.

If `msg_type` is 1, 2, 4, or 11 $\rightarrow$ `is_sotdma` = True, `is_itdma` = False.
If `msg_type` is 3 $\rightarrow$ `is_sotdma` = False, `is_itdma` = True.
If `msg_type` is 9, 18, or 26 $\rightarrow$ use `self.radio <= self.MAX_COMM_STATE_VALUE`.

Wait, what if `msg_type` is something else?
The `CommunicationStateMixin` is only mixed into these specific types.

Let's implement this in `CommunicationStateMixin`.

```python
    @property
    def is_sotdma(self) -> bool:
        """The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits"""
        if self.msg_type in (1, 2, 4, 11):
            return True
        if self.msg_type == 3:
            return False
        return self.radio <= self.MAX_COMM_STATE_VALUE

    @property
    def is_itdma(self) -> bool:
        """The radio status field has it's 20th bit (MSB) set to 1"""
        if self.msg_type in (1, 2, 4, 11):
            return False
        if self.msg_type == 3:
            return True
        return self.radio > self.MAX_COMM_STATE_VALUE
```

Wait, the current `is_itdma` is just `not is_sotdma` in essence?
Actually, `is_itdma` is `self.radio > self.MAX_COMM_STATE_VALUE`.

Let's see if `is_itdma` should always be the opposite of `is_sotdma`?
In the current implementation:
`is_sotdma` is `self.radio <= self.MAX_COMM_STATE_VALUE`.
`is_itdma` is `self.radio > self.MAX_COMM_STATE_VALUE`.
Yes, they are mutually exclusive and exhaustive for any `self.radio` value.

Wait, let's check the types again.
SOTDMA: 1, 2, 4, 9, 11, 18
ITDMA: 3, 18
(From the docs)

Problem says:
SOTDMA: (implied)
ITDMA: (implied)
Mixed: 9, 18, 26

Actually, the problem says: "In the case of a type 1, 2 or 3 message, it would be checked whether the RAIM flag is set. Since 1 is the default value, pyais returns in almost all cases that it is a SOTDMA com state."
This means for type 1, 2, 3, the current `self.radio` might actually be containing the RAIM flag, not the communication state.

Wait, if `self.radio` contains the RAIM flag for type 1, 2, 3, then `self.radio` is not the communication state.
But the properties `is_sotdma` and `is_itdma` are supposed to tell us if the communication state is SOTDMA or ITDMA.

If for type 1, 2, it's ALWAYS SOTDMA, and for type 3 it's ALWAYS ITDMA.
And for type 4, 11? The docs said SOTDMA.

Let's refine the logic:
```python
    @property
    def is_sotdma(self) -> bool:
        if self.msg_type in (1, 2, 4, 11):
            return True
        if self.msg_type == 3:
            return False
        return self.radio <= self.MAX_COMM_STATE_VALUE

    @property
    def is_itdma(self) -> bool:
        if self.msg_type in (1, 2, 4, 11):
            return False
        if self.msg_type == 3:
            return True
        return self.radio > self.MAX_COMM_STATE_VALUE
```

Wait, the problem statement specifically mentioned 9, 18, 26 as edge cases.
So for these, the bit length check is correct.

For type 1, 2, 3, 4, 11, the message type determines it.
Type 1, 2, 4, 11 -> SOTDMA.
Type 3 -> ITDMA.

Is this correct?
Let's check the docs again.
"The following messages have the SOTDMA communication state: Type 1, 2, 4, 9, 11, 18"
"The following messages have the ITDMA communication state: Type 3, 18"

So:
Type 1: SOTDMA
Type 2: SOTDMA
Type 3: ITDMA
Type 4: SOTDMA
Type 11: SOTDMA
Type 9: SOTDMA (but can be mixed)
Type 18: Mixed
Type 26: Mixed

Wait, if Type 9 is usually SOTDMA but can be mixed, and 18 can be mixed, and 26 can be mixed.
And the problem says "Type 9, 18 and 26 messages are edge cases, as they can include one of the two com state types. The existing implementation can be used here".

So for 9, 18, 26, we use `self.radio <= self.MAX_COMM_STATE_VALUE`.

What about others?
1, 2, 4, 11 -> always SOTDMA?
3 -> always ITDMA?

Let's check the repro example: `!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51`.
Msg type is 3. It should be ITDMA.
With my proposed logic:
`msg_type == 3` -> `is_sotdma = False`, `is_itdma = True`.
Correct!

Now let's verify this with a SOTDMA message.
Actually, I can just use a message of type 1.
Let's try to find an example of a type 1 message.
Typical AIS type 1 is `!AIVDM,1,1,,A,1234567890ABCDEF,0*XX`.
Wait, I can just create one or use the `pyais` library to encode one.

Actually, I'll just test with a known type 1 message.

Wait, I should also check if there are other types that might use `CommunicationStateMixin`.
The list I got from `grep` were: 1, 2, 3, 4, 9, 11, 18, 26.

So if `msg_type` is not in (9, 18, 26), then:
If it's (1, 2, 4, 11) -> SOTDMA
If it's (3) -> ITDMA

Wait, what if `msg_type` is not in any of these?
The `CommunicationStateMixin` is only mixed into these types anyway, so `self.msg_type` will be one of these if the object is an instance of a class using this mixin.

Let's double check if there are any other types.
The grep showed:
`MessageType1`, `MessageType2`, `MessageType3`, `MessageType4`, `MessageType9`, `MessageType11`, `MessageType18`, `MessageType26...`
All of them use `CommunicationStateMixin`.

So:
SOTDMA if `msg_type` in (1, 2, 4, 11)
ITDMA if `msg_type` == 3
Else (9, 18, 26) use bit check.

Wait, are there any other types?
Let's check if `MessageType26` is always mixed.
Yes, the problem says "Type 9, 18 and 26 messages are edge cases".

So the logic:
```python
    @property
    def is_sotdma(self) -> bool:
        if self.msg_type in (1, 2, 4, 11):
            return True
        if self.msg_type == 3:
            return False
        return self.radio <= self.MAX_COMM_STATE_VALUE
```
And
```python
    @property
    def is_itdma(self) -> bool:
        if self.msg_type in (1, 2, 4, 11):
            return False
        if self.msg_type == 3:
            return True
        return self.radio > self.MAX_COMM_STATE_VALUE
```

Wait, if `self.msg_type` is 18, it goes to `self.radio <= self.MAX_COMM_STATE_VALUE`.
If `self.radio` is 0 (which is $\le 0x7ffff$), it is SOTDMA.
If `self.radio` is $2^{19}$ (which is $0x80000$), it is ITDMA.
This matches the "existing implementation can be used here" for 9, 18, 26.

Let's check if this covers all cases.
Types using `CommunicationStateMixin`: 1, 2, 3, 4, 9, 11, 18, 26.
Our logic:
(1, 2, 4, 11) -> Fixed SOTDMA
(3) -> Fixed ITDMA
(9, 18, 26) -> Use `self.radio`

This seems to cover all of them.

Wait, what if `self.msg_type` is something else but the class still mixes in `CommunicationStateMixin`?
The current grep showed only these.

Let's implement the fix.

But first, let's make sure `self.msg_type` is available in `CommunicationStateMixin`.
In `pyais/messages.py`, `CommunicationStateMixin` is just a mixin.
The classes that inherit from it also inherit from `Payload`.
Let's see where `msg_type` is defined.
It seems each `MessageTypeX` class defines `msg_type`.

Let's check `MessageType1`.
TOOL CALL · run_command
{
  "command": "grep -C 5 \"class MessageType1\" pyais/messages.py"
}
Result of run_command 4,001 characters
{
  "status": "ok",
  "stdout": "                'Communication State is only available for messages with radio field'\n            ) from err\n\n\n@attr.s(slots=True)\nclass MessageType1(Payload, CommunicationStateMixin):\n    \"\"\"\n    AIS Vessel position report using SOTDMA (Self-Organizing Time Division Multiple Access)\n    Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_types_1_2_and_3_position_report_class_a\n    \"\"\"\n    msg_type = bit_field(6, int, default=1, signed=False)\n--\n    raim = bit_field(1, bool, default=0)\n    radio = bit_field(20, int, default=0, signed=False)\n\n\n@attr.s(slots=True)\nclass MessageType10(Payload):\n    \"\"\"\n    UTC/Date Inquiry\n    Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_type_10_utc_date_inquiry\n    \"\"\"\n    msg_type = bit_field(6, int, default=10, signed=False)\n--\n    spare_1 = bit_field(2, bytes, default=b'')\n    dest_mmsi = bit_field(30, int, from_converter=from_mmsi)\n    spare_2 = bit_field(2, bytes, default=b'')\n\n\nclass MessageType11(MessageType4):\n    \"\"\"\n    UTC/Date Response\n    Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_type_11_utc_date_response\n    \"\"\"\n    msg_type = bit_field(6, int, default=11, signed=False)\n\n\n@attr.s(slots=True)\nclass MessageType12(Payload):\n    \"\"\"\n    Addressed Safety-Related Message\n    Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_type_12_addressed_safety_related_message\n    \"\"\"\n    msg_type = bit_field(6, int, default=12, signed=False)\n--\n    retransmit = bit_field(1, bool, default=False, signed=False)\n    spare_1 = bit_field(1, bytes, default=b'')\n    text = bit_field(936, str, default='', variable_length=True)\n\n\nclass MessageType13(MessageType7):\n    \"\"\"\n    Identical to type 7\n    \"\"\"\n    msg_type = bit_field(6, int, default=13, signed=False)\n\n\n@attr.s(slots=True)\nclass MessageType14(Payload):\n    \"\"\"\n    Safety-Related Broadcast Message\n    Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_type_14_safety_related_broadcast_message\n    \"\"\"\n    msg_type = bit_field(6, int, default=14, signed=False)\n--\n    spare_1 = bit_field(2, bytes, default=b'')\n    text = bit_field(968, str, default='', variable_length=True)\n\n\n@attr.s(slots=True)\nclass MessageType15(Payload):\n    \"\"\"\n    Interrogation\n    Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_type_15_interrogation\n    \"\"\"\n    msg_type = bit_field(6, int, default=15, signed=False)\n--\n    offset2_1 = bit_field(12, int, default=0, signed=False)\n    spare_4 = bit_field(2, bytes, default=b'')\n\n\n@attr.s(slots=True)\nclass MessageType16(Payload):\n    \"\"\"\n    Assignment Mode Command\n    Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_type_16_assignment_mode_command\n    \"\"\"\n    msg_type = bit_field(6, int, default=16, signed=False)\n--\n    offset2 = bit_field(12, int, default=0, signed=False)\n    increment2 = bit_field(10, int, default=0, signed=False)\n\n\n@attr.s(slots=True)\nclass MessageType17(Payload):\n    \"\"\"\n    DGNSS Broadcast Binary Message\n    Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_type_17_dgnss_broadcast_binary_message\n    \"\"\"\n    msg_type = bit_field(6, int, default=17, signed=False)\n--\n    spare_2 = bit_field(5, bytes, default=b'')\n    data = bit_field(736, bytes, default=b'', variable_length=True)\n\n\n@attr.s(slots=True)\nclass MessageType18(Payload, CommunicationStateMixin):\n    \"\"\"\n    Standard Class B CS Position Report\n    Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_type_18_standard_class_b_cs_position_report\n    \"\"\"\n    msg_type = bit_field(6, int, default=18, signed=False)\n--\n    raim = bit_field(1, bool, default=0)\n    radio = bit_field(20, int, default=0)\n\n\n@attr.s(slots=True)\nclass MessageType19(Payload):\n    \"\"\"\n    Extended Class B CS Position Report\n    Src: https://gpsd.gitlab.io/gpsd/AIVDM.html#_type_19_extended_class_b_cs_position_report\n    \"\"\"\n    msg_type = bit_field(6, int, default=19, signed=False)\n",
  "stderr": "",
  "exit_code": 0
}
Generated tokens 2,824 tokens · stopped: STOP
Okay, so: `MessageType1` inherits `CommunicationStateMixin`. `MessageType2` inherits `MessageType1` $\rightarrow$ `CommunicationStateMixin`. `MessageType3` inherits `MessageType1` $\rightarrow$ `CommunicationStateMixin`. `MessageType4` inherits `CommunicationStateMixin`. `MessageType11` inherits `MessageType4` $\rightarrow$ `CommunicationStateMixin`. `MessageType9` inherits `CommunicationStateMixin`. `MessageType18` inherits `CommunicationStateMixin`. `MessageType26...` inherit `CommunicationStateMixin`. Now, based on the problem description: SOTDMA: 1, 2, 4, 11 ITDMA: 3 Edge cases (both): 9, 18, 26 Wait, let me re-read. "Table 46 of the M.1371-5 standard shows which type uses which com state. Type 9, 18 and 26 messages are edge cases, as they can include one of the two com state types. The existing implementation can be used here, as with these message types the bit before the actual com state determines its type." So for types 9, 18, 26, the existing `is_sotdma` implementation (which checks `self.radio <= self.MAX_COMM_STATE_VALUE`) is correct. For other types, we should use the message type. Types using `CommunicationStateMixin` are: 1, 2, 3, 4, 9, 11, 18, 26. If `msg_type` is 1, 2, 4, or 11 $\rightarrow$ `is_sotdma` = True, `is_itdma` = False. If `msg_type` is 3 $\rightarrow$ `is_sotdma` = False, `is_itdma` = True. If `msg_type` is 9, 18, or 26 $\rightarrow$ use `self.radio <= self.MAX_COMM_STATE_VALUE`. Wait, what if `msg_type` is something else? The `CommunicationStateMixin` is only mixed into these specific types. Let's implement this in `CommunicationStateMixin`. ```python @property def is_sotdma(self) -> bool: """The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits""" if self.msg_type in (1, 2, 4, 11): return True if self.msg_type == 3: return False return self.radio <= self.MAX_COMM_STATE_VALUE @property def is_itdma(self) -> bool: """The radio status field has it's 20th bit (MSB) set to 1""" if self.msg_type in (1, 2, 4, 11): return False if self.msg_type == 3: return True return self.radio > self.MAX_COMM_STATE_VALUE ``` Wait, the current `is_itdma` is just `not is_sotdma` in essence? Actually, `is_itdma` is `self.radio > self.MAX_COMM_STATE_VALUE`. Let's see if `is_itdma` should always be the opposite of `is_sotdma`? In the current implementation: `is_sotdma` is `self.radio <= self.MAX_COMM_STATE_VALUE`. `is_itdma` is `self.radio > self.MAX_COMM_STATE_VALUE`. Yes, they are mutually exclusive and exhaustive for any `self.radio` value. Wait, let's check the types again. SOTDMA: 1, 2, 4, 9, 11, 18 ITDMA: 3, 18 (From the docs) Problem says: SOTDMA: (implied) ITDMA: (implied) Mixed: 9, 18, 26 Actually, the problem says: "In the case of a type 1, 2 or 3 message, it would be checked whether the RAIM flag is set. Since 1 is the default value, pyais returns in almost all cases that it is a SOTDMA com state." This means for type 1, 2, 3, the current `self.radio` might actually be containing the RAIM flag, not the communication state. Wait, if `self.radio` contains the RAIM flag for type 1, 2, 3, then `self.radio` is not the communication state. But the properties `is_sotdma` and `is_itdma` are supposed to tell us if the communication state is SOTDMA or ITDMA. If for type 1, 2, it's ALWAYS SOTDMA, and for type 3 it's ALWAYS ITDMA. And for type 4, 11? The docs said SOTDMA. Let's refine the logic: ```python @property def is_sotdma(self) -> bool: if self.msg_type in (1, 2, 4, 11): return True if self.msg_type == 3: return False return self.radio <= self.MAX_COMM_STATE_VALUE @property def is_itdma(self) -> bool: if self.msg_type in (1, 2, 4, 11): return False if self.msg_type == 3: return True return self.radio > self.MAX_COMM_STATE_VALUE ``` Wait, the problem statement specifically mentioned 9, 18, 26 as edge cases. So for these, the bit length check is correct. For type 1, 2, 3, 4, 11, the message type determines it. Type 1, 2, 4, 11 -> SOTDMA. Type 3 -> ITDMA. Is this correct? Let's check the docs again. "The following messages have the SOTDMA communication state: Type 1, 2, 4, 9, 11, 18" "The following messages have the ITDMA communication state: Type 3, 18" So: Type 1: SOTDMA Type 2: SOTDMA Type 3: ITDMA Type 4: SOTDMA Type 11: SOTDMA Type 9: SOTDMA (but can be mixed) Type 18: Mixed Type 26: Mixed Wait, if Type 9 is usually SOTDMA but can be mixed, and 18 can be mixed, and 26 can be mixed. And the problem says "Type 9, 18 and 26 messages are edge cases, as they can include one of the two com state types. The existing implementation can be used here". So for 9, 18, 26, we use `self.radio <= self.MAX_COMM_STATE_VALUE`. What about others? 1, 2, 4, 11 -> always SOTDMA? 3 -> always ITDMA? Let's check the repro example: `!AIVDM,1,1,,A,333ga6PP0jPPDUNM3I7DTOwDR59C,0*51`. Msg type is 3. It should be ITDMA. With my proposed logic: `msg_type == 3` -> `is_sotdma = False`, `is_itdma = True`. Correct! Now let's verify this with a SOTDMA message. Actually, I can just use a message of type 1. Let's try to find an example of a type 1 message. Typical AIS type 1 is `!AIVDM,1,1,,A,1234567890ABCDEF,0*XX`. Wait, I can just create one or use the `pyais` library to encode one. Actually, I'll just test with a known type 1 message. Wait, I should also check if there are other types that might use `CommunicationStateMixin`. The list I got from `grep` were: 1, 2, 3, 4, 9, 11, 18, 26. So if `msg_type` is not in (9, 18, 26), then: If it's (1, 2, 4, 11) -> SOTDMA If it's (3) -> ITDMA Wait, what if `msg_type` is not in any of these? The `CommunicationStateMixin` is only mixed into these types anyway, so `self.msg_type` will be one of these if the object is an instance of a class using this mixin. Let's double check if there are any other types. The grep showed: `MessageType1`, `MessageType2`, `MessageType3`, `MessageType4`, `MessageType9`, `MessageType11`, `MessageType18`, `MessageType26...` All of them use `CommunicationStateMixin`. So: SOTDMA if `msg_type` in (1, 2, 4, 11) ITDMA if `msg_type` == 3 Else (9, 18, 26) use bit check. Wait, are there any other types? Let's check if `MessageType26` is always mixed. Yes, the problem says "Type 9, 18 and 26 messages are edge cases". So the logic: ```python @property def is_sotdma(self) -> bool: if self.msg_type in (1, 2, 4, 11): return True if self.msg_type == 3: return False return self.radio <= self.MAX_COMM_STATE_VALUE ``` And ```python @property def is_itdma(self) -> bool: if self.msg_type in (1, 2, 4, 11): return False if self.msg_type == 3: return True return self.radio > self.MAX_COMM_STATE_VALUE ``` Wait, if `self.msg_type` is 18, it goes to `self.radio <= self.MAX_COMM_STATE_VALUE`. If `self.radio` is 0 (which is $\le 0x7ffff$), it is SOTDMA. If `self.radio` is $2^{19}$ (which is $0x80000$), it is ITDMA. This matches the "existing implementation can be used here" for 9, 18, 26. Let's check if this covers all cases. Types using `CommunicationStateMixin`: 1, 2, 3, 4, 9, 11, 18, 26. Our logic: (1, 2, 4, 11) -> Fixed SOTDMA (3) -> Fixed ITDMA (9, 18, 26) -> Use `self.radio` This seems to cover all of them. Wait, what if `self.msg_type` is something else but the class still mixes in `CommunicationStateMixin`? The current grep showed only these. Let's implement the fix. But first, let's make sure `self.msg_type` is available in `CommunicationStateMixin`. In `pyais/messages.py`, `CommunicationStateMixin` is just a mixin. The classes that inherit from it also inherit from `Payload`. Let's see where `msg_type` is defined. It seems each `MessageTypeX` class defines `msg_type`. Let's check `MessageType1`. <channel|><|tool_call>call:run_command{command:<|"|>grep -C 5 "class MessageType1" pyais/messages.py<|"|>}<tool_call|><|tool_response>
Turn 17at 382 s586 tokens out14,397 prompt tokens
Yes, `msg_type` is defined in the classes.

Now let's implement the fix in `CommunicationStateMixin`.

Wait, I should check if there are any other types that should be handled.
The docs mention:
SOTDMA: 1, 2, 4, 9, 11, 18
ITDMA: 3, 18

According to the problem description, 9, 18, 26 are mixed.
So:
1, 2, 4, 11 -> SOTDMA
3 -> ITDMA
9, 18, 26 -> Mixed (bit check)

Wait, are there any others?
Let's see all types using `CommunicationStateMixin`:
1, 2, 3, 4, 9, 11, 18, 26.
That's all of them.
TOOL CALL · edit_file
{
  "filepath": "pyais/messages.py",
  "new_string": "    @property\n    def is_sotdma(self) -> bool:\n        \\\"\\\"\\\"The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits\\\"\\\"\\\"\n        if self.msg_type in (1, 2, 4, 11):\n            return True\n        if self.msg_type == 3:\n            return False\n        return self.radio <= self.MAX_COMM_STATE_VALUE\n\n    @property\n    def is_itdma(self) -> bool:\n        \\\"\\\"\\\"The radio status field has it's 20th bit (MSB) set to 1\\\"\\\"\\\"\n        if self.msg_type in (1, 2, 4, 11):\n            return False\n        if self.msg_type == 3:\n            return True\n        return self.radio > self.MAX_COMM_STATE_VALUE",
  "old_string": "    @property\n    def is_sotdma(self) -> bool:\n        \\\"\\\"\\\"The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits\\\"\\\"\\\"\n        return self.radio <= self.MAX_COMM_STATE_VALUE\n\n    @property\n    def is_itdma(self) -> bool:\n        \\\"\\\"\\\"The radio status field has it's 20th bit (MSB) set to 1\\\"\\\"\\\"\n        return self.radio > self.MAX_COMM_STATE_VALUE"
}
Result of edit_file 879 characters
{
  "status": "ok",
  "filepath": "pyais/messages.py",
  "occurrences": 1,
  "strategy": "unescaped",
  "diff": "--- a/pyais/messages.py\n+++ b/pyais/messages.py\n@@ -826,11 +826,19 @@\n     @property\n     def is_sotdma(self) -> bool:\n         \"\"\"The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits\"\"\"\n+        if self.msg_type in (1, 2, 4, 11):\n+            return True\n+        if self.msg_type == 3:\n+            return False\n         return self.radio <= self.MAX_COMM_STATE_VALUE\n \n     @property\n     def is_itdma(self) -> bool:\n         \"\"\"The radio status field has it's 20th bit (MSB) set to 1\"\"\"\n+        if self.msg_type in (1, 2, 4, 11):\n+            return False\n+        if self.msg_type == 3:\n+            return True\n         return self.radio > self.MAX_COMM_STATE_VALUE\n \n     @property\n",
  "is_truncated": false
}
Generated tokens 586 tokens · stopped: STOP
Yes, `msg_type` is defined in the classes. Now let's implement the fix in `CommunicationStateMixin`. Wait, I should check if there are any other types that should be handled. The docs mention: SOTDMA: 1, 2, 4, 9, 11, 18 ITDMA: 3, 18 According to the problem description, 9, 18, 26 are mixed. So: 1, 2, 4, 11 -> SOTDMA 3 -> ITDMA 9, 18, 26 -> Mixed (bit check) Wait, are there any others? Let's see all types using `CommunicationStateMixin`: 1, 2, 3, 4, 9, 11, 18, 26. That's all of them.<channel|><|tool_call>call:edit_file{filepath:<|"|>pyais/messages.py<|"|>,new_string:<|"|> @property def is_sotdma(self) -> bool: \"\"\"The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits\"\"\" if self.msg_type in (1, 2, 4, 11): return True if self.msg_type == 3: return False return self.radio <= self.MAX_COMM_STATE_VALUE @property def is_itdma(self) -> bool: \"\"\"The radio status field has it's 20th bit (MSB) set to 1\"\"\" if self.msg_type in (1, 2, 4, 11): return False if self.msg_type == 3: return True return self.radio > self.MAX_COMM_STATE_VALUE<|"|>,old_string:<|"|> @property def is_sotdma(self) -> bool: \"\"\"The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits\"\"\" return self.radio <= self.MAX_COMM_STATE_VALUE @property def is_itdma(self) -> bool: \"\"\"The radio status field has it's 20th bit (MSB) set to 1\"\"\" return self.radio > self.MAX_COMM_STATE_VALUE<|"|>}<tool_call|><|tool_response>

Patch, tests and logs

Final code change Included
diff --git a/pyais/messages.py b/pyais/messages.py
index 3f6ef48..84f5838 100644
--- a/pyais/messages.py
+++ b/pyais/messages.py
@@ -826,11 +826,19 @@ class CommunicationStateMixin:
     @property
     def is_sotdma(self) -> bool:
         """The radio status field has it's 20th bit (MSB) set to 0 or has less than 20 bits"""
+        if self.msg_type in (1, 2, 4, 11):
+            return True
+        if self.msg_type == 3:
+            return False
         return self.radio <= self.MAX_COMM_STATE_VALUE
 
     @property
     def is_itdma(self) -> bool:
         """The radio status field has it's 20th bit (MSB) set to 1"""
+        if self.msg_type in (1, 2, 4, 11):
+            return False
+        if self.msg_type == 3:
+            return True
         return self.radio > self.MAX_COMM_STATE_VALUE
 
     @property
Test output Included
============================= test session starts ==============================
collected 302 items

tests/test_constants.py ........                                         [  2%]
tests/test_decode.py ................................................... [ 19%]
....................................................                     [ 36%]
tests/test_decode_raw.py ........                                        [ 39%]
tests/test_encode.py ................................................... [ 56%]
..........                                                               [ 59%]
tests/test_examples.py .                                                 [ 59%]
tests/test_file_stream.py .s..........                                   [ 63%]
tests/test_filters.py ...............                                    [ 68%]
tests/test_flags.py .........                                            [ 71%]
tests/test_generic_stream.py ...........                                 [ 75%]
tests/test_main.py ....                                                  [ 76%]
tests/test_nmea.py ........................                              [ 84%]
tests/test_socket.py ......                                              [ 86%]
tests/test_tag_block.py ..........                                       [ 90%]
tests/test_talker_ids.py ..........                                      [ 93%]
tests/test_tcp_stream.py ..s                                             [ 94%]
tests/test_tracker.py ................                                   [ 99%]
tests/test_udp_stream.py .                                               [100%]

==================================== PASSES ====================================
_______________________ TestExamples.test_run_every_file _______________________
----------------------------- Captured stdout call -----------------------------
MessageType1(msg_type=1, repeat=0, mmsi=367380120, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.1, accuracy=False, lon=-122.404333, lat=37.806948, course=245.2, heading=511, second=59, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=34958)
MessageType5(msg_type=5, repeat=0, mmsi=368060190, ais_version=2, imo=0, callsign='WDK4954', shipname='P/V_GOLDEN_GATE', ship_type=<ShipType.PilotVessel: 50>, to_bow=14, to_stern=7, to_port=4, to_starboard=2, epfd=<EpfdType.Internal_GNSS: 15>, month=0, day=0, hour=24, minute=60, draught=0.0, destination='', dte=False, spare_1=b'\x00')
MessageType1(msg_type=1, repeat=0, mmsi=367380120, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.1, accuracy=False, lon=-122.404333, lat=37.806948, course=245.2, heading=511, second=59, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=34958)
{'msg_type': 1, 'repeat': 0, 'mmsi': 367380120, 'status': <NavigationStatus.UnderWayUsingEngine: 0>, 'turn': <TurnRate.NO_TI_DEFAULT: -128.0>, 'speed': 0.1, 'accuracy': False, 'lon': -122.404333, 'lat': 37.806948, 'course': 245.2, 'heading': 511, 'second': 59, 'maneuver': <ManeuverIndicator.NotAvailable: 0>, 'spare_1': b'\x00', 'raim': True, 'radio': 34958}
{
    "msg_type": 1,
    "repeat": 0,
    "mmsi": 367380120,
    "status": 0,
    "turn": -128.0,
    "speed": 0.1,
    "accuracy": false,
    "lon": -122.404333,
    "lat": 37.806948,
    "course": 245.2,
    "heading": 511,
    "second": 59,
    "maneuver": 0,
    "spare_1": "AA==",
    "raim": true,
    "radio": 34958
}
MessageType1(msg_type=1, repeat=0, mmsi=227006760, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=False, lon=0.13138, lat=49.475577, course=36.7, heading=511, second=14, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=False, radio=22136)
MessageType1(msg_type=1, repeat=0, mmsi=205448890, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=True, lon=4.419442, lat=51.237658, course=63.3, heading=511, second=15, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=2248)
MessageType1(msg_type=1, repeat=0, mmsi=786434, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=1.6, accuracy=True, lon=5.320033, lat=51.967037, course=112.0, heading=511, second=15, maneuver=<ManeuverIndicator.NoSpecialManeuver: 1>, spare_1=b'\x00', raim=False, radio=153208)
MessageType1(msg_type=1, repeat=0, mmsi=249191000, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=True, lon=23.603633, lat=37.955883, course=247.0, heading=511, second=12, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'@', raim=False, radio=22136)
MessageType1(msg_type=1, repeat=0, mmsi=316013198, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=True, lon=-130.316237, lat=54.32111, course=237.9, heading=511, second=16, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=81935)
MessageType1(msg_type=1, repeat=0, mmsi=366913120, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=0.0, speed=0.0, accuracy=False, lon=-64.620662, lat=18.321188, course=329.5, heading=299, second=16, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=98890)
The raw radio value is: 393222
Communication state is SOTMDA: True
Communication state is ITDMA: False
Communication state: {
    "received_stations": null,
    "slot_number": null,
    "utc_hour": null,
    "utc_minute": null,
    "slot_offset": 6,
    "slot_timeout": 0,
    "sync_state": 3,
    "keep_flag": null,
    "slot_increment": null,
    "num_slots": null
}
MessageType1(msg_type=1, repeat=0, mmsi=227006760, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=False, lon=0.13138, lat=49.475577, course=36.7, heading=511, second=14, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=False, radio=22136)
MessageType1(msg_type=1, repeat=0, mmsi=205448890, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=True, lon=4.419442, lat=51.237658, course=63.3, heading=511, second=15, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=2248)
MessageType1(msg_type=1, repeat=0, mmsi=786434, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=1.6, accuracy=True, lon=5.320033, lat=51.967037, course=112.0, heading=511, second=15, maneuver=<ManeuverIndicator.NoSpecialManeuver: 1>, spare_1=b'\x00', raim=False, radio=153208)
MessageType1(msg_type=1, repeat=0, mmsi=249191000, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=True, lon=23.603633, lat=37.955883, course=247.0, heading=511, second=12, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'@', raim=False, radio=22136)
MessageType1(msg_type=1, repeat=0, mmsi=316013198, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=True, lon=-130.316237, lat=54.32111, course=237.9, heading=511, second=16, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=81935)
MessageType1(msg_type=1, repeat=0, mmsi=366913120, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=0.0, speed=0.0, accuracy=False, lon=-64.620662, lat=18.321188, course=329.5, heading=299, second=16, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=98890)
MessageType1(msg_type=1, repeat=0, mmsi=367078248, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=0.0, speed=34.6, accuracy=True, lon=-73.88158, lat=28.230963, course=181.2, heading=192, second=4, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\xa0', raim=False, radio=96)
(Attribute(name='msg_type', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 6, 'd_type': <class 'int'>, 'from_converter': None, 'to_converter': None, 'signed': False, 'default': 1, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='msg_type'), Attribute(name='repeat', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 2, 'd_type': <class 'int'>, 'from_converter': None, 'to_converter': None, 'signed': False, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='repeat'), Attribute(name='mmsi', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 30, 'd_type': <class 'int'>, 'from_converter': <function from_mmsi at 0x71f1532a0160>, 'to_converter': None, 'signed': False, 'default': None, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='mmsi'), Attribute(name='status', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 4, 'd_type': <class 'int'>, 'from_converter': None, 'to_converter': None, 'signed': False, 'default': 0, 'variable_length': False}), type=None, converter=<bound method NavigationStatus.from_value of <enum 'NavigationStatus'>>, kw_only=False, inherited=False, on_setattr=None, alias='status'), Attribute(name='turn', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 8, 'd_type': <class 'float'>, 'from_converter': <function from_turn at 0x71f1532a0280>, 'to_converter': <function to_turn at 0x71f1532a01f0>, 'signed': True, 'default': <TurnRate.NO_TI_DEFAULT: -128.0>, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='turn'), Attribute(name='speed', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 10, 'd_type': <class 'float'>, 'from_converter': <function from_speed at 0x71f1532948b0>, 'to_converter': <function to_speed at 0x71f15329dd30>, 'signed': False, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='speed'), Attribute(name='accuracy', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 1, 'd_type': <class 'bool'>, 'from_converter': None, 'to_converter': None, 'signed': False, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='accuracy'), Attribute(name='lon', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 28, 'd_type': <class 'float'>, 'from_converter': <function from_lat_lon at 0x71f15329ddc0>, 'to_converter': <function to_lat_lon at 0x71f15329de50>, 'signed': True, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='lon'), Attribute(name='lat', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 27, 'd_type': <class 'float'>, 'from_converter': <function from_lat_lon at 0x71f15329ddc0>, 'to_converter': <function to_lat_lon at 0x71f15329de50>, 'signed': True, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='lat'), Attribute(name='course', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 12, 'd_type': <class 'float'>, 'from_converter': <function from_10th at 0x71f1532a0040>, 'to_converter': <function to_10th at 0x71f1532a00d0>, 'signed': False, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='course'), Attribute(name='heading', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 9, 'd_type': <class 'int'>, 'from_converter': None, 'to_converter': None, 'signed': False, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='heading'), Attribute(name='second', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 6, 'd_type': <class 'int'>, 'from_converter': None, 'to_converter': None, 'signed': False, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='second'), Attribute(name='maneuver', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 2, 'd_type': <class 'int'>, 'from_converter': <bound method ManeuverIndicator.from_value of <enum 'ManeuverIndicator'>>, 'to_converter': <bound method ManeuverIndicator.from_value of <enum 'ManeuverIndicator'>>, 'signed': False, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='maneuver'), Attribute(name='spare_1', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 3, 'd_type': <class 'bytes'>, 'from_converter': None, 'to_converter': None, 'signed': False, 'default': b'', 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='spare_1'), Attribute(name='raim', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 1, 'd_type': <class 'bool'>, 'from_converter': None, 'to_converter': None, 'signed': False, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='raim'), Attribute(name='radio', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({'width': 19, 'd_type': <class 'int'>, 'from_converter': None, 'to_converter': None, 'signed': False, 'default': 0, 'variable_length': False}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None, alias='radio'))
['!AIVDO,1,1,,A,15M67F@P00G?ur@E`FL8T@000000,0*06']
['!AIVDO,1,1,,B,15M67F@P00G?ur@E`FL8T@000000,0*05']
['!AIVDO,1,1,,A,15M67F@P00G?ur@E`FL8T@000000,0*06']
['!AIVDO,1,1,,B,15M67F@P00G?ur@E`FL8T@000000,0*05']
AISSentence<!AIVDM,1,1,,B,15N4cJ005Jrek0H@9nDW5608EP,013>
{'raw': b'g:1-2-73874,n:157036,s:r003669945,c:12415440354*A', 'receiver_timestamp': '12415440354', 'source_station': 'r003669945', 'destination_station': None, 'line_count': '157036', 'relative_time': None, 'text': None}
{'raw': b'g:1-2-73874,n:157036,s:r003669945,c:12415440354*A', 'receiver_timestamp': '12415440354', 'source_station': 'r003669945', 'destination_station': None, 'line_count': '157036', 'relative_time': None, 'text': None}
MessageType1(msg_type=1, repeat=0, mmsi=366053209, status=<NavigationStatus.RestrictedManoeuverability: 3>, turn=0.0, speed=0.0, accuracy=False, lon=-122.341618, lat=37.802118, course=219.3, heading=1, second=59, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=False, radio=2281)
MessageType1(msg_type=1, repeat=0, mmsi=366053209, status=<NavigationStatus.RestrictedManoeuverability: 3>, turn=0.0, speed=0.0, accuracy=False, lon=-122.341618, lat=37.802118, course=219.3, heading=1, second=59, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=False, radio=2281)
MessageType5(msg_type=5, repeat=0, mmsi=210035000, ais_version=0, imo=9514755, callsign='5BXT2', shipname='NORDIC HAMBURG', ship_type=<ShipType.Cargo_HazardousCategory_A: 71>, to_bow=142, to_stern=10, to_port=11, to_starboard=11, epfd=<EpfdType.GPS: 1>, month=7, day=20, hour=5, minute=0, draught=7.1, destination='CTT-LAYBY', dte=False, spare_1=b'\x00')
MessageType5(msg_type=5, repeat=0, mmsi=210035000, ais_version=0, imo=9514755, callsign='5BXT2', shipname='NORDIC HAMBURG', ship_type=<ShipType.Cargo_HazardousCategory_A: 71>, to_bow=142, to_stern=10, to_port=11, to_starboard=11, epfd=<EpfdType.GPS: 1>, month=7, day=20, hour=5, minute=0, draught=7.1, destination='CTT-LAYBY', dte=False, spare_1=b'\x00')
latest 10 tracks 367078248,366913120,316013198,249191000,786434,205448890,227006760
AISTrack(mmsi=249191000, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, lon=23.603633, lat=37.955883, course=247.0, heading=511, imo=None, callsign=None, shipname=None, ship_type=None, to_bow=None, to_stern=None, to_port=None, to_starboard=None, destination=None, last_updated=1794629611.6192951, name=None, ais_version=None, ais_type=None, status=<NavigationStatus.UnderWayUsingEngine: 0>)
********************************************************************************
Country 338
Online 1
PSS 
Region 2
Timestamp 2008-05-09 00:00:00.010000
MessageType1(msg_type=1, repeat=0, mmsi=367309370, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=11.1, accuracy=False, lon=-122.765622, lat=48.177737, course=138.5, heading=511, second=0, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=False, radio=49181)
********************************************************************************
MessageType1(msg_type=1, repeat=0, mmsi=413355820, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=True, lon=119.698612, lat=39.932017, course=342.1, heading=259, second=14, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=False, radio=33328)
********************************************************************************
Country 338
Online 1
PSS 
Region 2
Timestamp 2009-05-09 00:00:00.010000
MessageType1(msg_type=1, repeat=0, mmsi=445451000, status=<NavigationStatus.UnderWaySailing: 8>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=3.9, accuracy=False, lon=120.365158, lat=35.997872, course=310.5, heading=511, second=13, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=False, radio=99017)
********************************************************************************
MessageType1(msg_type=1, repeat=0, mmsi=412764890, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=0.0, speed=11.1, accuracy=True, lon=122.515033, lat=31.652833, course=4.5, heading=0, second=14, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=False, radio=33329)
______________________ TestMainApp.test_decode_from_file _______________________
----------------------------- Captured stdout call -----------------------------
MessageType1(msg_type=1, repeat=0, mmsi=227006760, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=False, lon=0.13138, lat=49.475577, course=36.7, heading=511, second=14, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=False, radio=22136)
MessageType1(msg_type=1, repeat=0, mmsi=205448890, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=True, lon=4.419442, lat=51.237658, course=63.3, heading=511, second=15, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=2248)
MessageType1(msg_type=1, repeat=0, mmsi=786434, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=1.6, accuracy=True, lon=5.320033, lat=51.967037, course=112.0, heading=511, second=15, maneuver=<ManeuverIndicator.NoSpecialManeuver: 1>, spare_1=b'\x00', raim=False, radio=153208)
MessageType1(msg_type=1, repeat=0, mmsi=249191000, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=True, lon=23.603633, lat=37.955883, course=247.0, heading=511, second=12, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'@', raim=False, radio=22136)
MessageType1(msg_type=1, repeat=0, mmsi=316013198, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=<TurnRate.NO_TI_DEFAULT: -128.0>, speed=0.0, accuracy=True, lon=-130.316237, lat=54.32111, course=237.9, heading=511, second=16, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=81935)
MessageType1(msg_type=1, repeat=0, mmsi=366913120, status=<NavigationStatus.UnderWayUsingEngine: 0>, turn=0.0, speed=0.0, accuracy=False, lon=-64.620662, lat=18.321188, course=329.5, heading=299, second=16, maneuver=<ManeuverIndicator.NotAvailable: 0>, spare_1=b'\x00', raim=True, radio=98890)
MessageType5(msg_type=5, repeat=0, mmsi=351759000, ais_version=0, imo=9134270, callsign='3FOF8', shipname='EVER DIADEM', ship_type=<ShipType.Cargo: 70>, to_bow=225, to_stern=70, to_port=1, to_starboard=31, epfd=<EpfdType.GPS: 1>, month=5, day=15, hour=14, minute=0, draught=12.2, destination='NEW YORK', dte=False, spare_1=b'\x00')
________________________ TestMainApp.test_decode_single ________________________
----------------------------- Captured stdout call -----------------------------
MessageType9(msg_type=9, repeat=0, mmsi=111232511, alt=303, speed=42.0, accuracy=False, lon=-6.278843, lat=58.144, course=154.5, second=15, reserved_1=0, dte=True, spare_1=b'\x00', assigned=False, raim=False, radio=33392)
_____________________ TestMainApp.test_decode_single_multi _____________________
----------------------------- Captured stdout call -----------------------------
MessageType5(msg_type=5, repeat=0, mmsi=210035000, ais_version=0, imo=9514755, callsign='5BXT2', shipname='NORDIC HAMBURG', ship_type=<ShipType.Cargo_HazardousCategory_A: 71>, to_bow=142, to_stern=10, to_port=11, to_starboard=11, epfd=<EpfdType.GPS: 1>, month=7, day=20, hour=5, minute=0, draught=7.1, destination='CTT-LAYBY', dte=False, spare_1=b'\x00')
MessageType5(msg_type=5, repeat=0, mmsi=210035000, ais_version=0, imo=9514755, callsign='5BXT2', shipname='NORDIC HAMBURG', ship_type=<ShipType.Cargo_HazardousCategory_A: 71>, to_bow=142, to_stern=10, to_port=11, to_starboard=11, epfd=<EpfdType.GPS: 1>, month=7, day=20, hour=5, minute=0, draught=7.1, destination='CTT-LAYBY', dte=False, spare_1=b'\x00')
___________________________ TestMainApp.test_parser ____________________________
----------------------------- Captured stderr call -----------------------------
usage: ais-decode [-h] [-f [IN_FILE]] [-o OUT_FILE] {socket,single} ...
ais-decode: error: argument -f/--file: can't open 'invalid': [Errno 2] No such file or directory: 'invalid'
usage: ais-decode single [-h] messages [messages ...]
ais-decode single: error: the following arguments are required: messages
usage: ais-decode socket [-h] [-t [{udp,tcp}]] destination port
ais-decode socket: error: argument -t/--type: invalid choice: 'something else' (choose from 'udp', 'tcp')
- generated xml file: /tmp/rebench-grade-7674a32bf63d470db879bb94d0f3a032.xml --
=========================== short test summary info ============================
PASSED tests/test_constants.py::TestConstants::test_maneuver_indication
PASSED tests/test_constants.py::TestConstants::test_nav_status
PASSED tests/test_constants.py::TestConstants::test_navaid
PASSED tests/test_constants.py::TestConstants::test_ship_types
PASSED tests/test_constants.py::TestConstants::test_station_intervals
PASSED tests/test_constants.py::TestConstants::test_station_types
PASSED tests/test_constants.py::TestConstants::test_t_mode
PASSED tests/test_constants.py::TestConstants::test_that_nav_data_values_are_kept_for_all_values
PASSED tests/test_decode.py::TestAIS::test_b64encode_str
PASSED tests/test_decode.py::TestAIS::test_b64encode_str_empty
PASSED tests/test_decode.py::TestAIS::test_bits2bytes
PASSED tests/test_decode.py::TestAIS::test_broken_messages
PASSED tests/test_decode.py::TestAIS::test_byte_stream
PASSED tests/test_decode.py::TestAIS::test_bytes2bits
PASSED tests/test_decode.py::TestAIS::test_common_invalid_inputs_to_the_decode_function
PASSED tests/test_decode.py::TestAIS::test_decode_1_speed
PASSED tests/test_decode.py::TestAIS::test_decode_and_merge
PASSED tests/test_decode.py::TestAIS::test_decode_does_not_raise_an_error_by_default
PASSED tests/test_decode.py::TestAIS::test_decode_does_not_raise_an_error_if_error_if_checksum_invalid_is_false
PASSED tests/test_decode.py::TestAIS::test_decode_does_raise_an_error_if_error_if_checksum_invalid_is_true
PASSED tests/test_decode.py::TestAIS::test_decode_into_bit_array_with_non_printable_characters
PASSED tests/test_decode.py::TestAIS::test_decode_out_of_order
PASSED tests/test_decode.py::TestAIS::test_decode_pos_1_2_3
PASSED tests/test_decode.py::TestAIS::test_empty_channel
PASSED tests/test_decode.py::TestAIS::test_get_comm_state_type_18_itdma_base_indirect
PASSED tests/test_decode.py::TestAIS::test_get_comm_state_type_18_sotdma_base_inidrect
PASSED tests/test_decode.py::TestAIS::test_get_comm_state_type_18_sotdma_utc_direct
PASSED tests/test_decode.py::TestAIS::test_get_sotdma_comm_state_utc_direct
PASSED tests/test_decode.py::TestAIS::test_get_sotdma_comm_state_utc_direct_slot_number
PASSED tests/test_decode.py::TestAIS::test_get_sotdma_comm_state_utc_direct_slot_timeout
PASSED tests/test_decode.py::TestAIS::test_gh_ais_message_decode
PASSED tests/test_decode.py::TestAIS::test_invalid_timestamp_message
PASSED tests/test_decode.py::TestAIS::test_is_sotdma_or_itdma
PASSED tests/test_decode.py::TestAIS::test_issue_46_a
PASSED tests/test_decode.py::TestAIS::test_issue_46_b
PASSED tests/test_decode.py::TestAIS::test_issue_50
PASSED tests/test_decode.py::TestAIS::test_issue_88
PASSED tests/test_decode.py::TestAIS::test_messages_with_proprietary_suffix
PASSED tests/test_decode.py::TestAIS::test_msg_too_short_enum_is_none
PASSED tests/test_decode.py::TestAIS::test_msg_type
PASSED tests/test_decode.py::TestAIS::test_msg_type_10_a
PASSED tests/test_decode.py::TestAIS::test_msg_type_10_b
PASSED tests/test_decode.py::TestAIS::test_msg_type_11
PASSED tests/test_decode.py::TestAIS::test_msg_type_12_a
PASSED tests/test_decode.py::TestAIS::test_msg_type_12_b
PASSED tests/test_decode.py::TestAIS::test_msg_type_13
PASSED tests/test_decode.py::TestAIS::test_msg_type_14
PASSED tests/test_decode.py::TestAIS::test_msg_type_15_a
PASSED tests/test_decode.py::TestAIS::test_msg_type_15_b
PASSED tests/test_decode.py::TestAIS::test_msg_type_16
PASSED tests/test_decode.py::TestAIS::test_msg_type_17_a
PASSED tests/test_decode.py::TestAIS::test_msg_type_17_b
PASSED tests/test_decode.py::TestAIS::test_msg_type_17_to_json
PASSED tests/test_decode.py::TestAIS::test_msg_type_18
PASSED tests/test_decode.py::TestAIS::test_msg_type_18_speed
PASSED tests/test_decode.py::TestAIS::test_msg_type_19
PASSED tests/test_decode.py::TestAIS::test_msg_type_1_a
PASSED tests/test_decode.py::TestAIS::test_msg_type_1_b
PASSED tests/test_decode.py::TestAIS::test_msg_type_1_c
PASSED tests/test_decode.py::TestAIS::test_msg_type_20
PASSED tests/test_decode.py::TestAIS::test_msg_type_21
PASSED tests/test_decode.py::TestAIS::test_msg_type_22_addressed
PASSED tests/test_decode.py::TestAIS::test_msg_type_22_broadcast
PASSED tests/test_decode.py::TestAIS::test_msg_type_23
PASSED tests/test_decode.py::TestAIS::test_msg_type_24
PASSED tests/test_decode.py::TestAIS::test_msg_type_24_with_160_bits
PASSED tests/test_decode.py::TestAIS::test_msg_type_24_with_168_bits
PASSED tests/test_decode.py::TestAIS::test_msg_type_25_a
PASSED tests/test_decode.py::TestAIS::test_msg_type_25_b
PASSED tests/test_decode.py::TestAIS::test_msg_type_25_c
PASSED tests/test_decode.py::TestAIS::test_msg_type_25_to_json
PASSED tests/test_decode.py::TestAIS::test_msg_type_26_a
PASSED tests/test_decode.py::TestAIS::test_msg_type_26_b
PASSED tests/test_decode.py::TestAIS::test_msg_type_26_to_json
PASSED tests/test_decode.py::TestAIS::test_msg_type_27
PASSED tests/test_decode.py::TestAIS::test_msg_type_27_signed
PASSED tests/test_decode.py::TestAIS::test_msg_type_3
PASSED tests/test_decode.py::TestAIS::test_msg_type_4_a
PASSED tests/test_decode.py::TestAIS::test_msg_type_4_b
PASSED tests/test_decode.py::TestAIS::test_msg_type_5
PASSED tests/test_decode.py::TestAIS::test_msg_type_6
PASSED tests/test_decode.py::TestAIS::test_msg_type_6_json_reverse
PASSED tests/test_decode.py::TestAIS::test_msg_type_6_to_json
PASSED tests/test_decode.py::TestAIS::test_msg_type_7
PASSED tests/test_decode.py::TestAIS::test_msg_type_8
PASSED tests/test_decode.py::TestAIS::test_msg_type_8_multipart
PASSED tests/test_decode.py::TestAIS::test_msg_type_8_to_json
PASSED tests/test_decode.py::TestAIS::test_msg_type_9
PASSED tests/test_decode.py::TestAIS::test_msg_with_more_that_82_chars_payload
PASSED tests/test_decode.py::TestAIS::test_multiline_message
PASSED tests/test_decode.py::TestAIS::test_nmea_decode
PASSED tests/test_decode.py::TestAIS::test_nmea_decode_unknown_msg
PASSED tests/test_decode.py::TestAIS::test_none_value_converter_for_creation
PASSED tests/test_decode.py::TestAIS::test_none_value_converter_for_decoding
PASSED tests/test_decode.py::TestAIS::test_none_values_converter_for_all_messages
PASSED tests/test_decode.py::TestAIS::test_rot_decode_yields_expected_values
PASSED tests/test_decode.py::TestAIS::test_rot_encode_decode
PASSED tests/test_decode.py::TestAIS::test_rot_encode_yields_expected_values
PASSED tests/test_decode.py::TestAIS::test_sotdma_time_conversion
PASSED tests/test_decode.py::TestAIS::test_special_position_report
PASSED tests/test_decode.py::TestAIS::test_static_data_report
PASSED tests/test_decode.py::TestAIS::test_that_decode_nmea_and_ais_works_with_proprietary_messages
PASSED tests/test_decode.py::TestAIS::test_that_lat_and_long_are_rounded_correctly
PASSED tests/test_decode.py::TestAIS::test_that_the_payload_does_not_change_when_encoding_decoding
PASSED tests/test_decode.py::TestAIS::test_timestamp_message
PASSED tests/test_decode.py::TestAIS::test_to_dict_non_enum
PASSED tests/test_decode.py::TestAIS::test_to_json
PASSED tests/test_decode.py::TestAIS::test_type_22_very_short
PASSED tests/test_decode.py::TestAIS::test_type_25_very_short
PASSED tests/test_decode.py::TestAIS::test_type_26_very_short
PASSED tests/test_decode.py::TestAIS::test_types_for_messages
PASSED tests/test_decode_raw.py::TestDecode::test_bytes_invalid
PASSED tests/test_decode_raw.py::TestDecode::test_bytes_valid
PASSED tests/test_decode_raw.py::TestDecode::test_decode_multiline_message
PASSED tests/test_decode_raw.py::TestDecode::test_decode_total_garbage
PASSED tests/test_decode_raw.py::TestDecode::test_multipart_error_message
PASSED tests/test_decode_raw.py::TestDecode::test_str_invalid
PASSED tests/test_decode_raw.py::TestDecode::test_str_valid
PASSED tests/test_decode_raw.py::TestDecode::test_too_many_messages
PASSED tests/test_encode.py::test_widths
PASSED tests/test_encode.py::test_variable_message_length_width
PASSED tests/test_encode.py::test_encode_msg_table
PASSED tests/test_encode.py::test_invalid_talker_id
PASSED tests/test_encode.py::test_encode_payload_invalid_talker_id
PASSED tests/test_encode.py::test_invalid_radio_channel
PASSED tests/test_encode.py::test_encode_payload_error_radio
PASSED tests/test_encode.py::test_data_to_payload
PASSED tests/test_encode.py::test_get_ais_type
PASSED tests/test_encode.py::test_str_to_bin
PASSED tests/test_encode.py::test_int_to_bin
PASSED tests/test_encode.py::test_decode_encode
PASSED tests/test_encode.py::test_encode_type_27
PASSED tests/test_encode.py::test_encode_type_26
PASSED tests/test_encode.py::test_encode_type_25_b
PASSED tests/test_encode.py::test_encode_type_25_a
PASSED tests/test_encode.py::test_encode_type_24_partno_invalid
PASSED tests/test_encode.py::test_encode_type_24_a
PASSED tests/test_encode.py::test_encode_type_24_b
PASSED tests/test_encode.py::test_encode_type_23
PASSED tests/test_encode.py::test_encode_type_22_b
PASSED tests/test_encode.py::test_encode_type_22_a
PASSED tests/test_encode.py::test_encode_type_21
PASSED tests/test_encode.py::test_encode_type_20
PASSED tests/test_encode.py::test_encode_type_19
PASSED tests/test_encode.py::test_encode_type_18_with_speed_and_course
PASSED tests/test_encode.py::test_encode_type_18
PASSED tests/test_encode.py::test_encode_type_17_b
PASSED tests/test_encode.py::test_encode_type_17_a
PASSED tests/test_encode.py::test_encode_type_16
PASSED tests/test_encode.py::test_encode_type_15_a
PASSED tests/test_encode.py::test_encode_type_15
PASSED tests/test_encode.py::test_encode_type_14
PASSED tests/test_encode.py::test_encode_type_13
PASSED tests/test_encode.py::test_encode_type_12_with_some_text
PASSED tests/test_encode.py::test_encode_type_12_with_no_text
PASSED tests/test_encode.py::test_encode_type_12_with_empty_text
PASSED tests/test_encode.py::test_encode_type_12_with_max_length_text
PASSED tests/test_encode.py::test_encode_type_12_with_longer_than_max_length_length_text
PASSED tests/test_encode.py::test_encode_type_11
PASSED tests/test_encode.py::test_encode_type_10
PASSED tests/test_encode.py::test_encode_type_9
PASSED tests/test_encode.py::test_encode_type_8
PASSED tests/test_encode.py::test_encode_type_7
PASSED tests/test_encode.py::test_encode_type_6_bytes
PASSED tests/test_encode.py::test_encode_type_6
PASSED tests/test_encode.py::test_encode_type_4
PASSED tests/test_encode.py::test_encode_type_5_issue_59
PASSED tests/test_encode.py::test_encode_type_5
PASSED tests/test_encode.py::test_encode_type_5_default
PASSED tests/test_encode.py::test_encode_msg_type2
PASSED tests/test_encode.py::test_encode_msg_type_3
PASSED tests/test_encode.py::test_encode_type_1_default
PASSED tests/test_encode.py::test_encode_type_1
PASSED tests/test_encode.py::test_mmsi_too_long
PASSED tests/test_encode.py::test_lon_too_large
PASSED tests/test_encode.py::test_ship_name_too_lon
PASSED tests/test_encode.py::test_int_to_bytes
PASSED tests/test_encode.py::test_to_six_bit
PASSED tests/test_encode.py::test_encode_ascii_6_bit
PASSED tests/test_encode.py::test_encode_does_not_exceed_nmea_sentence_length_limit
PASSED tests/test_examples.py::TestExamples::test_run_every_file
PASSED tests/test_file_stream.py::TestFileReaderStream::test_invalid_filename
PASSED tests/test_file_stream.py::TestFileReaderStream::test_marine_traffic_sample
PASSED tests/test_file_stream.py::TestFileReaderStream::test_mixed_content
PASSED tests/test_file_stream.py::TestFileReaderStream::test_nmea_sort_index_error
PASSED tests/test_file_stream.py::TestFileReaderStream::test_nmea_sort_invalid_frag_cnt
PASSED tests/test_file_stream.py::TestFileReaderStream::test_nmea_sorter_sorted
PASSED tests/test_file_stream.py::TestFileReaderStream::test_nmea_sorter_unsorted
PASSED tests/test_file_stream.py::TestFileReaderStream::test_reader
PASSED tests/test_file_stream.py::TestFileReaderStream::test_reader_with_open
PASSED tests/test_file_stream.py::TestFileReaderStream::test_sort_with_different_msg_ids
PASSED tests/test_file_stream.py::TestFileReaderStream::test_timestamp_messages
PASSED tests/test_filters.py::TestNoneFilter::test_filtering_missing_attributes
PASSED tests/test_filters.py::TestNoneFilter::test_filtering_none_attributes
PASSED tests/test_filters.py::TestMessageTypeFilter::test_message_type_filtering
PASSED tests/test_filters.py::TestDistanceFilter::test_all_within_distance
PASSED tests/test_filters.py::TestDistanceFilter::test_distance_filtering
PASSED tests/test_filters.py::TestDistanceFilter::test_none_within_distance
PASSED tests/test_filters.py::TestDistanceFilter::test_specific_coordinate_distance
PASSED tests/test_filters.py::TestGridFilter::test_central_park_grid
PASSED tests/test_filters.py::TestGridFilter::test_grid_filtering
PASSED tests/test_filters.py::TestFilterChain::test_complex_filter_chain
PASSED tests/test_filters.py::TestFilterChain::test_filter_chain
PASSED tests/test_filters.py::TestAttributeFilter::test_attribute_filtering
PASSED tests/test_filters.py::TestHaversine::test_london_to_paris
PASSED tests/test_filters.py::TestHaversine::test_nyc_to_la
PASSED tests/test_filters.py::TestHaversine::test_sydney_to_melbourne
PASSED tests/test_flags.py::MMSICountryCodeTestCase::test_get_three_digits
PASSED tests/test_flags.py::MMSICountryCodeTestCase::test_random_ship_1
PASSED tests/test_flags.py::MMSICountryCodeTestCase::test_random_ship_2
PASSED tests/test_flags.py::MMSICountryCodeTestCase::test_random_ship_3
PASSED tests/test_flags.py::MMSICountryCodeTestCase::test_random_ship_4
PASSED tests/test_flags.py::MMSICountryCodeTestCase::test_random_ship_5
PASSED tests/test_flags.py::MMSICountryCodeTestCase::test_random_ship_6
PASSED tests/test_flags.py::MMSICountryCodeTestCase::test_random_ship_7
PASSED tests/test_flags.py::MMSICountryCodeTestCase::test_random_ship_8
PASSED tests/test_generic_stream.py::TestGenericStream::test_empty_stream
PASSED tests/test_generic_stream.py::TestGenericStream::test_garbage_stream
PASSED tests/test_generic_stream.py::TestGenericStream::test_invalid_msg
PASSED tests/test_generic_stream.py::TestIterMessages::test_init_from_bytes_transforms_bytes_to_list
PASSED tests/test_generic_stream.py::TestIterMessages::test_init_from_bytes_with_multiple_messages
PASSED tests/test_generic_stream.py::TestIterMessages::test_init_from_ignores_error_if_enabled
PASSED tests/test_generic_stream.py::TestIterMessages::test_init_from_str
PASSED tests/test_generic_stream.py::TestIterMessages::test_init_from_str_throws_error_if_invalid
PASSED tests/test_generic_stream.py::TestIterMessages::test_iter_is_generator
PASSED tests/test_generic_stream.py::TestIterMessages::test_iter_messages_does_assemble_multiline_messages
PASSED tests/test_generic_stream.py::TestIterMessages::test_iter_messages_handles_single_message
PASSED tests/test_main.py::TestMainApp::test_decode_from_file
PASSED tests/test_main.py::TestMainApp::test_decode_single
PASSED tests/test_main.py::TestMainApp::test_decode_single_multi
PASSED tests/test_main.py::TestMainApp::test_parser
PASSED tests/test_nmea.py::TestNMEA::test_attrs
PASSED tests/test_nmea.py::TestNMEA::test_chk_to_int_with_fill_bits
PASSED tests/test_nmea.py::TestNMEA::test_chk_to_int_with_missing_checksum
PASSED tests/test_nmea.py::TestNMEA::test_chk_to_int_with_missing_fill_bits
PASSED tests/test_nmea.py::TestNMEA::test_chk_to_int_with_valid_checksum
PASSED tests/test_nmea.py::TestNMEA::test_dict
PASSED tests/test_nmea.py::TestNMEA::test_from_bytes
PASSED tests/test_nmea.py::TestNMEA::test_from_str
PASSED tests/test_nmea.py::TestNMEA::test_get_item
PASSED tests/test_nmea.py::TestNMEA::test_get_item_raises_key_error
PASSED tests/test_nmea.py::TestNMEA::test_get_item_raises_type_error
PASSED tests/test_nmea.py::TestNMEA::test_message_assembling
PASSED tests/test_nmea.py::TestNMEA::test_message_eq_method
PASSED tests/test_nmea.py::TestNMEA::test_missing_checksum
PASSED tests/test_nmea.py::TestNMEA::test_single
PASSED tests/test_nmea.py::TestNMEA::test_talker
PASSED tests/test_nmea.py::TestNMEA::test_that_a_valid_checksum_is_correctly_identified
PASSED tests/test_nmea.py::TestNMEA::test_that_a_valid_checksum_is_correctly_identified_for_multi_part_msgs
PASSED tests/test_nmea.py::TestNMEA::test_that_an_invalid_checksum_is_correctly_identified
PASSED tests/test_nmea.py::TestNMEA::test_that_an_invalid_checksum_is_correctly_identified_for_multi_part_msgs
PASSED tests/test_nmea.py::TestNMEA::test_type
PASSED tests/test_nmea.py::TestNMEA::test_validity
PASSED tests/test_nmea.py::TestNMEA::test_values
PASSED tests/test_nmea.py::TestNMEA::test_wrong_type
PASSED tests/test_socket.py::SocketStreamingTestCase::test_that_a_single_line_is_returned_as_is
PASSED tests/test_socket.py::SocketStreamingTestCase::test_that_all_lines_are_returned
PASSED tests/test_socket.py::SocketStreamingTestCase::test_that_all_lines_are_returned_for_incomplete_lines
PASSED tests/test_socket.py::SocketStreamingTestCase::test_that_all_lines_are_returned_when_using_crlf
PASSED tests/test_socket.py::SocketStreamingTestCase::test_that_many_lines_are_returned
PASSED tests/test_socket.py::SocketStreamingTestCase::test_that_no_partial_artifacts_are_added_after_a_complete_line
PASSED tests/test_tag_block.py::TagBlockTestCase::test_multiple_messages
PASSED tests/test_tag_block.py::TagBlockTestCase::test_spire_maritime_format
PASSED tests/test_tag_block.py::TagBlockTestCase::test_tag_block_with_line_count
PASSED tests/test_tag_block.py::TagBlockTestCase::test_tag_block_with_multiple_unknown_fields
PASSED tests/test_tag_block.py::TagBlockTestCase::test_that_a_tag_block_is_lazily_evaluated
PASSED tests/test_tag_block.py::TagBlockTestCase::test_that_the_factory_is_gentle_with_malformed_tag_blocks
PASSED tests/test_tag_block.py::TagBlockTestCase::test_that_the_factory_pre_processes_correctly
PASSED tests/test_tag_block.py::TagBlockTestCase::test_that_the_factory_removes_the_leading_tag_block
PASSED tests/test_tag_block.py::TagBlockTestCase::test_that_unknown_tag_blocks_are_ignored
PASSED tests/test_tag_block.py::TagBlockTestCase::test_that_unknown_tag_blocks_can_exported_as_dicts
PASSED tests/test_talker_ids.py::TestTalkerIds::test_ab
PASSED tests/test_talker_ids.py::TestTalkerIds::test_ad
PASSED tests/test_talker_ids.py::TestTalkerIds::test_ai
PASSED tests/test_talker_ids.py::TestTalkerIds::test_an
PASSED tests/test_talker_ids.py::TestTalkerIds::test_ar
PASSED tests/test_talker_ids.py::TestTalkerIds::test_as
PASSED tests/test_talker_ids.py::TestTalkerIds::test_at
PASSED tests/test_talker_ids.py::TestTalkerIds::test_ax
PASSED tests/test_talker_ids.py::TestTalkerIds::test_bs
PASSED tests/test_talker_ids.py::TestTalkerIds::test_sa
PASSED tests/test_tcp_stream.py::TestTCPStream::test_default_buf_size
PASSED tests/test_tcp_stream.py::TestTCPStream::test_invalid_endpoint
PASSED tests/test_tracker.py::TrackerTestCase::test_that_clean_up_deletes_tracks_if_they_expire
PASSED tests/test_tracker.py::TrackerTestCase::test_that_clean_up_does_nothing_if_ttl_is_none
PASSED tests/test_tracker.py::TrackerTestCase::test_that_get_track_returns_track_if_it_exists
PASSED tests/test_tracker.py::TrackerTestCase::test_that_insert_or_update_sets_oldest_timestamp
PASSED tests/test_tracker.py::TrackerTestCase::test_that_n_latest_returns_empty_list_for_empty
PASSED tests/test_tracker.py::TrackerTestCase::test_that_n_latest_returns_tracks_in_correct_order
PASSED tests/test_tracker.py::TrackerTestCase::test_that_ordered_tracker_handles_cleanup
PASSED tests/test_tracker.py::TrackerTestCase::test_that_ordered_tracker_raises_value_error_if_older_record_is_received
PASSED tests/test_tracker.py::TrackerTestCase::test_that_ordered_tracker_returns_correct_latest_n
PASSED tests/test_tracker.py::TrackerTestCase::test_that_pop_tracks_returns_and_deletes_track_if_it_exists
PASSED tests/test_tracker.py::TrackerTestCase::test_that_poplast_is_non_destructive
PASSED tests/test_tracker.py::TrackerTestCase::test_that_tracker_works_as_context_manager
PASSED tests/test_tracker.py::TrackerTestCase::test_that_tracks_property_returns_tracks_as_list
PASSED tests/test_tracker.py::TrackerTestCase::test_that_update_always_updates_all_fields
PASSED tests/test_tracker.py::TrackerTestCase::test_that_update_cleans_up_afterwards
PASSED tests/test_tracker.py::TrackerTestCase::test_that_update_raises_error_is_timestamp_is_younger
PASSED tests/test_udp_stream.py::TestOutOfOrder::test_stream
SKIPPED [1] tests/test_file_stream.py:154: Takes too long
SKIPPED [1] tests/test_tcp_stream.py:55: Skip for now, because there is a Threading problem
======================== 300 passed, 2 skipped in 1.16s ========================
Test errors Included
Test report (JUnit) Included
<?xml version="1.0" encoding="utf-8"?><testsuites><testsuite name="pytest" errors="0" failures="0" skipped="2" tests="302" time="1.147" timestamp="2026-11-14T04:13:30.745594+00:00" hostname="16b5624351ed"><testcase classname="tests.test_constants.TestConstants" name="test_maneuver_indication" time="0.000" /><testcase classname="tests.test_constants.TestConstants" name="test_nav_status" time="0.000" /><testcase classname="tests.test_constants.TestConstants" name="test_navaid" time="0.000" /><testcase classname="tests.test_constants.TestConstants" name="test_ship_types" time="0.000" /><testcase classname="tests.test_constants.TestConstants" name="test_station_intervals" time="0.000" /><testcase classname="tests.test_constants.TestConstants" name="test_station_types" time="0.000" /><testcase classname="tests.test_constants.TestConstants" name="test_t_mode" time="0.000" /><testcase classname="tests.test_constants.TestConstants" name="test_that_nav_data_values_are_kept_for_all_values" time="0.001" /><testcase classname="tests.test_decode.TestAIS" name="test_b64encode_str" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_b64encode_str_empty" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_bits2bytes" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_broken_messages" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_byte_stream" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_bytes2bits" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_common_invalid_inputs_to_the_decode_function" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_decode_1_speed" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_decode_and_merge" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_decode_does_not_raise_an_error_by_default" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_decode_does_not_raise_an_error_if_error_if_checksum_invalid_is_false" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_decode_does_raise_an_error_if_error_if_checksum_invalid_is_true" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_decode_into_bit_array_with_non_printable_characters" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_decode_out_of_order" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_decode_pos_1_2_3" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_empty_channel" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_get_comm_state_type_18_itdma_base_indirect" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_get_comm_state_type_18_sotdma_base_inidrect" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_get_comm_state_type_18_sotdma_utc_direct" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_get_sotdma_comm_state_utc_direct" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_get_sotdma_comm_state_utc_direct_slot_number" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_get_sotdma_comm_state_utc_direct_slot_timeout" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_gh_ais_message_decode" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_invalid_timestamp_message" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_is_sotdma_or_itdma" time="0.001" /><testcase classname="tests.test_decode.TestAIS" name="test_issue_46_a" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_issue_46_b" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_issue_50" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_issue_88" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_messages_with_proprietary_suffix" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_too_short_enum_is_none" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_10_a" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_10_b" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_11" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_12_a" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_12_b" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_13" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_14" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_15_a" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_15_b" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_16" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_17_a" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_17_b" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_17_to_json" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_18" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_18_speed" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_19" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_1_a" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_1_b" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_1_c" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_20" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_21" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_22_addressed" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_22_broadcast" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_23" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_24" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_24_with_160_bits" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_24_with_168_bits" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_25_a" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_25_b" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_25_c" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_25_to_json" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_26_a" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_26_b" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_26_to_json" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_27" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_27_signed" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_3" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_4_a" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_4_b" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_5" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_6" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_6_json_reverse" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_6_to_json" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_7" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_8" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_8_multipart" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_8_to_json" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_type_9" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_msg_with_more_that_82_chars_payload" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_multiline_message" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_nmea_decode" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_nmea_decode_unknown_msg" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_none_value_converter_for_creation" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_none_value_converter_for_decoding" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_none_values_converter_for_all_messages" time="0.001" /><testcase classname="tests.test_decode.TestAIS" name="test_rot_decode_yields_expected_values" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_rot_encode_decode" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_rot_encode_yields_expected_values" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_sotdma_time_conversion" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_special_position_report" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_static_data_report" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_that_decode_nmea_and_ais_works_with_proprietary_messages" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_that_lat_and_long_are_rounded_correctly" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_that_the_payload_does_not_change_when_encoding_decoding" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_timestamp_message" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_to_dict_non_enum" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_to_json" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_type_22_very_short" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_type_25_very_short" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_type_26_very_short" time="0.000" /><testcase classname="tests.test_decode.TestAIS" name="test_types_for_messages" time="0.000" /><testcase classname="tests.test_decode_raw.TestDecode" name="test_bytes_invalid" time="0.000" /><testcase classname="tests.test_decode_raw.TestDecode" name="test_bytes_valid" time="0.000" /><testcase classname="tests.test_decode_raw.TestDecode" name="test_decode_multiline_message" time="0.000" /><testcase classname="tests.test_decode_raw.TestDecode" name="test_decode_total_garbage" time="0.000" /><testcase classname="tests.test_decode_raw.TestDecode" name="test_multipart_error_message" time="0.000" /><testcase classname="tests.test_decode_raw.TestDecode" name="test_str_invalid" time="0.000" /><testcase classname="tests.test_decode_raw.TestDecode" name="test_str_valid" time="0.000" /><testcase classname="tests.test_decode_raw.TestDecode" name="test_too_many_messages" time="0.000" /><testcase classname="tests.test_encode" name="test_widths" time="0.000" /><testcase classname="tests.test_encode" name="test_variable_message_length_width" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_msg_table" time="0.000" /><testcase classname="tests.test_encode" name="test_invalid_talker_id" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_payload_invalid_talker_id" time="0.000" /><testcase classname="tests.test_encode" name="test_invalid_radio_channel" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_payload_error_radio" time="0.000" /><testcase classname="tests.test_encode" name="test_data_to_payload" time="0.000" /><testcase classname="tests.test_encode" name="test_get_ais_type" time="0.000" /><testcase classname="tests.test_encode" name="test_str_to_bin" time="0.000" /><testcase classname="tests.test_encode" name="test_int_to_bin" time="0.000" /><testcase classname="tests.test_encode" name="test_decode_encode" time="0.002" /><testcase classname="tests.test_encode" name="test_encode_type_27" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_26" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_25_b" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_25_a" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_24_partno_invalid" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_24_a" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_24_b" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_23" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_22_b" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_22_a" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_21" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_20" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_19" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_18_with_speed_and_course" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_18" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_17_b" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_17_a" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_16" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_15_a" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_15" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_14" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_13" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_12_with_some_text" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_12_with_no_text" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_12_with_empty_text" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_12_with_max_length_text" time="0.001" /><testcase classname="tests.test_encode" name="test_encode_type_12_with_longer_than_max_length_length_text" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_11" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_10" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_9" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_8" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_7" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_6_bytes" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_6" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_4" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_5_issue_59" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_5" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_5_default" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_msg_type2" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_msg_type_3" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_1_default" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_type_1" time="0.000" /><testcase classname="tests.test_encode" name="test_mmsi_too_long" time="0.000" /><testcase classname="tests.test_encode" name="test_lon_too_large" time="0.000" /><testcase classname="tests.test_encode" name="test_ship_name_too_lon" time="0.000" /><testcase classname="tests.test_encode" name="test_int_to_bytes" time="0.000" /><testcase classname="tests.test_encode" name="test_to_six_bit" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_ascii_6_bit" time="0.000" /><testcase classname="tests.test_encode" name="test_encode_does_not_exceed_nmea_sentence_length_limit" time="0.000" /><testcase classname="tests.test_examples.TestExamples" name="test_run_every_file" time="0.681" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_invalid_filename" time="0.000" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_large_file" time="0.000"><skipped type="pytest.skip" message="Takes too long">/testbed/tests/test_file_stream.py:154: Takes too long</skipped></testcase><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_marine_traffic_sample" time="0.019" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_mixed_content" time="0.000" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_nmea_sort_index_error" time="0.000" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_nmea_sort_invalid_frag_cnt" time="0.000" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_nmea_sorter_sorted" time="0.000" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_nmea_sorter_unsorted" time="0.000" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_reader" time="0.000" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_reader_with_open" time="0.000" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_sort_with_different_msg_ids" time="0.000" /><testcase classname="tests.test_file_stream.TestFileReaderStream" name="test_timestamp_messages" time="0.000" /><testcase classname="tests.test_filters.TestNoneFilter" name="test_filtering_missing_attributes" time="0.000" /><testcase classname="tests.test_filters.TestNoneFilter" name="test_filtering_none_attributes" time="0.000" /><testcase classname="tests.test_filters.TestMessageTypeFilter" name="test_message_type_filtering" time="0.000" /><testcase classname="tests.test_filters.TestDistanceFilter" name="test_all_within_distance" time="0.000" /><testcase classname="tests.test_filters.TestDistanceFilter" name="test_distance_filtering" time="0.000" /><testcase classname="tests.test_filters.TestDistanceFilter" name="test_none_within_distance" time="0.000" /><testcase classname="tests.test_filters.TestDistanceFilter" name="test_specific_coordinate_distance" time="0.000" /><testcase classname="tests.test_filters.TestGridFilter" name="test_central_park_grid" time="0.000" /><testcase classname="tests.test_filters.TestGridFilter" name="test_grid_filtering" time="0.000" /><testcase classname="tests.test_filters.TestFilterChain" name="test_complex_filter_chain" time="0.000" /><testcase classname="tests.test_filters.TestFilterChain" name="test_filter_chain" time="0.000" /><testcase classname="tests.test_filters.TestAttributeFilter" name="test_attribute_filtering" time="0.000" /><testcase classname="tests.test_filters.TestHaversine" name="test_london_to_paris" time="0.000" /><testcase classname="tests.test_filters.TestHaversine" name="test_nyc_to_la" time="0.000" /><testcase classname="tests.test_filters.TestHaversine" name="test_sydney_to_melbourne" time="0.000" /><testcase classname="tests.test_flags.MMSICountryCodeTestCase" name="test_get_three_digits" time="0.000" /><testcase classname="tests.test_flags.MMSICountryCodeTestCase" name="test_random_ship_1" time="0.000" /><testcase classname="tests.test_flags.MMSICountryCodeTestCase" name="test_random_ship_2" time="0.000" /><testcase classname="tests.test_flags.MMSICountryCodeTestCase" name="test_random_ship_3" time="0.000" /><testcase classname="tests.test_flags.MMSICountryCodeTestCase" name="test_random_ship_4" time="0.000" /><testcase classname="tests.test_flags.MMSICountryCodeTestCase" name="test_random_ship_5" time="0.000" /><testcase classname="tests.test_flags.MMSICountryCodeTestCase" name="test_random_ship_6" time="0.000" /><testcase classname="tests.test_flags.MMSICountryCodeTestCase" name="test_random_ship_7" time="0.000" /><testcase classname="tests.test_flags.MMSICountryCodeTestCase" name="test_random_ship_8" time="0.000" /><testcase classname="tests.test_generic_stream.TestGenericStream" name="test_empty_stream" time="0.000" /><testcase classname="tests.test_generic_stream.TestGenericStream" name="test_garbage_stream" time="0.000" /><testcase classname="tests.test_generic_stream.TestGenericStream" name="test_invalid_msg" time="0.000" /><testcase classname="tests.test_generic_stream.TestIterMessages" name="test_init_from_bytes_transforms_bytes_to_list" time="0.000" /><testcase classname="tests.test_generic_stream.TestIterMessages" name="test_init_from_bytes_with_multiple_messages" time="0.000" /><testcase classname="tests.test_generic_stream.TestIterMessages" name="test_init_from_ignores_error_if_enabled" time="0.000" /><testcase classname="tests.test_generic_stream.TestIterMessages" name="test_init_from_str" time="0.000" /><testcase classname="tests.test_generic_stream.TestIterMessages" name="test_init_from_str_throws_error_if_invalid" time="0.000" /><testcase classname="tests.test_generic_stream.TestIterMessages" name="test_iter_is_generator" time="0.000" /><testcase classname="tests.test_generic_stream.TestIterMessages" name="test_iter_messages_does_assemble_multiline_messages" time="0.000" /><testcase classname="tests.test_generic_stream.TestIterMessages" name="test_iter_messages_handles_single_message" time="0.000" /><testcase classname="tests.test_main.TestMainApp" name="test_decode_from_file" time="0.001" /><testcase classname="tests.test_main.TestMainApp" name="test_decode_single" time="0.000" /><testcase classname="tests.test_main.TestMainApp" name="test_decode_single_multi" time="0.000" /><testcase classname="tests.test_main.TestMainApp" name="test_parser" time="0.002" /><testcase classname="tests.test_nmea.TestNMEA" name="test_attrs" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_chk_to_int_with_fill_bits" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_chk_to_int_with_missing_checksum" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_chk_to_int_with_missing_fill_bits" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_chk_to_int_with_valid_checksum" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_dict" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_from_bytes" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_from_str" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_get_item" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_get_item_raises_key_error" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_get_item_raises_type_error" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_message_assembling" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_message_eq_method" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_missing_checksum" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_single" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_talker" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_that_a_valid_checksum_is_correctly_identified" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_that_a_valid_checksum_is_correctly_identified_for_multi_part_msgs" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_that_an_invalid_checksum_is_correctly_identified" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_that_an_invalid_checksum_is_correctly_identified_for_multi_part_msgs" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_type" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_validity" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_values" time="0.000" /><testcase classname="tests.test_nmea.TestNMEA" name="test_wrong_type" time="0.000" /><testcase classname="tests.test_socket.SocketStreamingTestCase" name="test_that_a_single_line_is_returned_as_is" time="0.000" /><testcase classname="tests.test_socket.SocketStreamingTestCase" name="test_that_all_lines_are_returned" time="0.000" /><testcase classname="tests.test_socket.SocketStreamingTestCase" name="test_that_all_lines_are_returned_for_incomplete_lines" time="0.000" /><testcase classname="tests.test_socket.SocketStreamingTestCase" name="test_that_all_lines_are_returned_when_using_crlf" time="0.000" /><testcase classname="tests.test_socket.SocketStreamingTestCase" name="test_that_many_lines_are_returned" time="0.000" /><testcase classname="tests.test_socket.SocketStreamingTestCase" name="test_that_no_partial_artifacts_are_added_after_a_complete_line" time="0.000" /><testcase classname="tests.test_tag_block.TagBlockTestCase" name="test_multiple_messages" time="0.000" /><testcase classname="tests.test_tag_block.TagBlockTestCase" name="test_spire_maritime_format" time="0.001" /><testcase classname="tests.test_tag_block.TagBlockTestCase" name="test_tag_block_with_line_count" time="0.000" /><testcase classname="tests.test_tag_block.TagBlockTestCase" name="test_tag_block_with_multiple_unknown_fields" time="0.000" /><testcase classname="tests.test_tag_block.TagBlockTestCase" name="test_that_a_tag_block_is_lazily_evaluated" time="0.000" /><testcase classname="tests.test_tag_block.TagBlockTestCase" name="test_that_the_factory_is_gentle_with_malformed_tag_blocks" time="0.000" /><testcase classname="tests.test_tag_block.TagBlockTestCase" name="test_that_the_factory_pre_processes_correctly" time="0.000" /><testcase classname="tests.test_tag_block.TagBlockTestCase" name="test_that_the_factory_removes_the_leading_tag_block" time="0.000" /><testcase classname="tests.test_tag_block.TagBlockTestCase" name="test_that_unknown_tag_blocks_are_ignored" time="0.000" /><testcase classname="tests.test_tag_block.TagBlockTestCase" name="test_that_unknown_tag_blocks_can_exported_as_dicts" time="0.000" /><testcase classname="tests.test_talker_ids.TestTalkerIds" name="test_ab" time="0.000" /><testcase classname="tests.test_talker_ids.TestTalkerIds" name="test_ad" time="0.000" /><testcase classname="tests.test_talker_ids.TestTalkerIds" name="test_ai" time="0.000" /><testcase classname="tests.test_talker_ids.TestTalkerIds" name="test_an" time="0.000" /><testcase classname="tests.test_talker_ids.TestTalkerIds" name="test_ar" time="0.000" /><testcase classname="tests.test_talker_ids.TestTalkerIds" name="test_as" time="0.000" /><testcase classname="tests.test_talker_ids.TestTalkerIds" name="test_at" time="0.000" /><testcase classname="tests.test_talker_ids.TestTalkerIds" name="test_ax" time="0.000" /><testcase classname="tests.test_talker_ids.TestTalkerIds" name="test_bs" time="0.000" /><testcase classname="tests.test_talker_ids.TestTalkerIds" name="test_sa" time="0.000" /><testcase classname="tests.test_tcp_stream.TestTCPStream" name="test_default_buf_size" time="0.000" /><testcase classname="tests.test_tcp_stream.TestTCPStream" name="test_invalid_endpoint" time="0.000" /><testcase classname="tests.test_tcp_stream.TestTCPStream" name="test_tcp_stream" time="0.000"><skipped type="pytest.skip" message="Skip for now, because there is a Threading problem">/testbed/tests/test_tcp_stream.py:55: Skip for now, because there is a Threading problem</skipped></testcase><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_clean_up_deletes_tracks_if_they_expire" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_clean_up_does_nothing_if_ttl_is_none" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_get_track_returns_track_if_it_exists" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_insert_or_update_sets_oldest_timestamp" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_n_latest_returns_empty_list_for_empty" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_n_latest_returns_tracks_in_correct_order" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_ordered_tracker_handles_cleanup" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_ordered_tracker_raises_value_error_if_older_record_is_received" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_ordered_tracker_returns_correct_latest_n" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_pop_tracks_returns_and_deletes_track_if_it_exists" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_poplast_is_non_destructive" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_tracker_works_as_context_manager" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_tracks_property_returns_tracks_as_list" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_update_always_updates_all_fields" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_update_cleans_up_afterwards" time="0.000" /><testcase classname="tests.test_tracker.TrackerTestCase" name="test_that_update_raises_error_is_timestamp_is_younger" time="0.000" /><testcase classname="tests.test_udp_stream.TestOutOfOrder" name="test_stream" time="0.101" /></testsuite></testsuites>
Agent log Included
  [   0.0s] 📋 System Prompt: You are a principal Python software engineer resolving a repository issue in `/workspace`.
  [   0.0s] 🎯 Task Prompt: You are evaluating a software engineering task for repository M0r13n/pyais.
  [  12.8s] 💭 (583 chars) "The user is reporting a bug in `pyais` regarding the communication status field detection.
Specifically, `is_sotdma` and..."
  [  12.8s] 🔧 run_command grep -rn --include="*.py" "def is_sotdma" .
  [  12.8s]   → ./pyais/messages.py:827:    def is_sotdma(self) -> bool:
  [  17.0s] 💭 (85 chars) "Okay, the properties are in `pyais/messages.py`. Let's examine the code around there."
  [  17.0s] 📖 read_file (pyais/messages.py)
  [  17.0s]   ✓ {'status': 'ok', 'filepath': 'pyais/messages.py', 'content': '            
result.update(get_sotdma_comm_state(self.commu…
  [  38.5s] 💭 (930 chars) "I need to see where `MAX_COMM_STATE_VALUE` is defined and what `self.radio` is.
Also, I need to see where `self.radio` i..."
  [  38.5s] 🔧 run_command grep -rn "MAX_COMM_STATE_VALUE" .
  [  38.5s]   → ./pyais/messages.py:802:    MAX_COMM_STATE_VALUE = 0x7ffff
  [  42.2s] 💭 (63 chars) "It's defined in `pyais/messages.py`.

Let's identify the class."
  [  42.2s] 📖 read_file (pyais/messages.py)
  [  42.2s]   ✓ {'status': 'ok', 'filepath': 'pyais/messages.py', 'content': '    radio: int  # Type hint to make mypy 
happy\n\n    MAX_…
  [  45.0s] 💭 (54 chars) "I still don't see the class name. Let's go further up."
  [  45.0s] 📖 read_file (pyais/messages.py)
  [  45.0s]   ✓ {'status': 'ok', 'filepath': 'pyais/messages.py', 'content': '    return math.copysign(round((turn / 
4.733) ** 2), turn)…
  [  58.5s] 💭 (302 chars) "The class is `CommunicationStateMixin`.

Now let's reproduce the bug with the provided example.

```python
import pyais
..."
  [  58.5s] 🔧 run_command (8 lines) python3 - <<'PY'
  [  58.7s]   → SOTDMA: True, ITDMA: False
  [  79.6s] 💭 (938 chars) "The problem statement says this should be ITDMA.
"the following example incorrectly indicates that it is a SOTDMA commun..."
  [  79.6s] 🔧 run_command (6 lines) python3 - <<'PY'
  [  79.7s]   → Type: N/A, Type2: N/A
  [  83.8s] 💭 (99 chars) "Hmm, it seems I don't have `msgtype` or `type`. Let's see what attributes the `decoded` 
object has."
  [  83.8s] 🔧 run_command (5 lines) python3 - <<'PY'
  [  83.9s]   → ['MAX_COMM_STATE_VALUE', '_Payload__force_type', '__abstractmethods__', '__annotations__', 
'__attrs_attrs__', '__attrs_o
  [  88.4s] 💭 (75 chars) "The attribute is `msg_type`.
Let's check the `msg_type` for the reproducer:"
  [  88.4s] 🔧 run_command (5 lines) python3 - <<'PY'
  [  88.5s]   → Msg Type: 3
  [ 126.0s] 💭 (1588 chars) "The msg type is 3.
The problem statement says: "In the case of a type 1, 2 or 3 message, it would be checked whether the..."
  [ 126.0s] 🔧 run_command grep -rn "msg_type" pyais/messages.py | head -n 20
  [ 126.1s]   → 547:        MessageType18(msg_type=18, ...)
  [ 146.3s] 💭 (1160 chars) "I need to find which message types are SOTDMA and which are ITDMA.
The problem description says "Table 46 of the M.1371-..."
  [ 146.3s] 🔧 run_command grep -rn "SOTDMA" .
  [ 146.3s]   → ./pyais/messages.py:850:    AIS Vessel position report using SOTDMA (Self-Organizing Time Division 
Multiple Access)
  [ 149.7s] 💭 (62 chars) "Wait, the `docs/messages.rst` might have the list of messages!"
  [ 149.7s] 🔧 run_command grep -A 20 "The following messages have the SOTDMA communication state:" docs/me
  [ 149.8s]   → The following messages have the SOTDMA communication state:
  [ 164.0s] 💭 (434 chars) "Okay! Now I have the list!
SOTDMA: 1, 2, 4, 9, 11, 18
ITDMA: 3, 18 (Wait, the docs say 18 is both)

Wait, the documentat..."
  [ 164.0s] 🔧 run_command grep -C 5 "Type 26" docs/messages.rst
  [ 164.0s]   → error (exit 0): 
  [ 186.7s] 💭 (966 chars) "Maybe I should just search for all mentions of "Type" in `docs/messages.rst` around 
communication state.

Actually, let'..."
  [ 186.7s] 🔧 run_command grep -rn "CommunicationStateMixin" pyais/messages.py
  [ 186.8s]   → 792:class CommunicationStateMixin:
  [ 197.0s] 💭 (372 chars) "Okay, the classes that use `CommunicationStateMixin` are:
`MessageType1`
`MessageType4` (Wait, where is MessageType2?)
`..."
  [ 197.0s] 🔧 run_command grep -rn "class MessageType" pyais/messages.py
  [ 197.1s]   → 848:class MessageType1(Payload, CommunicationStateMixin):
  [ 355.7s] 💭 (7985 chars) "Okay, so:
`MessageType1` inherits `CommunicationStateMixin`.
`MessageType2` inherits `MessageType1` $\rightarrow$ `Commu..."
  [ 355.7s] 🔧 run_command grep -C 5 "class MessageType1" pyais/messages.py
  [ 355.8s]   →                 'Communication State is only available for messages with radio field'
  [ 381.4s] 💭 (491 chars) "Yes, `msg_type` is defined in the classes.

Now let's implement the fix in `CommunicationStateMixin`.

Wait, I should ch..."
  [ 381.4s] ✏️ edit_file (pyais/messages.py)
  [ 381.6s]   ✓ {'status': 'ok', 'filepath': 'pyais/messages.py', 'occurrences': 1, 'strategy': 'unescaped', 'diff': 
'--- a/pyais/messag…
Native result row
{
  "agent_error": "Agent exceeded session timeout (7.5 min)",
  "duration_seconds": 454.87,
  "error": "Agent exceeded session timeout (7.5 min)",
  "grade": {
    "collected_tests": 302,
    "competition_junit_ok": true,
    "error": null,
    "required_issues_count": 0,
    "resolved": true,
    "status_counts": {
      "PASSED": 300,
      "SKIPPED": 2
    },
    "strict_clean_patch": true,
    "test_exit_code": 0
  },
  "instance_id": "M0r13n__pyais-137",
  "pass_idx": 0,
  "patch_chars": 830,
  "phase1_seconds": 452.77,
  "repo": "M0r13n/pyais",
  "request_log_path": "/kaggle-root/working/r31/rollouts_X/requests/M0r13n__pyais-137__p0__r2.jsonl",
  "resolved": true,
  "rollout_idx": 2,
  "run_key": "M0r13n__pyais-137__p0__r2",
  "strict_clean_patch": true,
  "tool_calls": 17,
  "total_llm_calls": 17,
  "trace_path": "/kaggle-root/working/r31/rollouts_X/traces/trace_M0r13n__pyais-137__p0__r2.json"
}