closed TG-1; git was inited;

This commit is contained in:
2026-05-04 08:13:38 +03:00
commit bcf20fcb04
105 changed files with 6592 additions and 0 deletions
+126
View File
@@ -0,0 +1,126 @@
"""State integration tests for AIS-catcher binary telemetry."""
from __future__ import annotations
from ais_hub.core.bus import EventBus
from ais_hub.core.state import State
from ais_hub.core.stats import Stats
from ais_hub.parser.aiscatcher import (
RssiIq,
SignalEvent,
SignalEventBatch,
SlotBitmap,
SlotDetail,
SlotLevel,
)
def _make_state() -> State:
stats = Stats()
bus = EventBus(stats=stats, default_maxsize=64)
return State(bus=bus, stats=stats)
def test_rssi_iq_updates_state_and_publishes():
state = _make_state()
sub = state._bus.subscribe(maxsize=16)
state.apply_rssi_iq(RssiIq(power_a_db=-41.5, power_b_db=-43.0), ts=123.0)
assert state.radio_power == {
"ts": 123.0,
"power_a_db": -41.5,
"power_b_db": -43.0,
}
assert not sub.empty()
ev = sub.get_nowait()
assert ev.type == "radio.update"
assert ev.data["source"] == "aiscatcher_rssi"
assert ev.data["power_a_db"] == -41.5
def test_slot_bitmap_stored_per_channel():
state = _make_state()
snap = SlotBitmap(
channel="A",
utc_minute=28279310,
slots_total=2250,
occupied_count=100,
noise_floor=0.0,
threshold=0.0,
slot0_unix_ms=28279310 * 60000,
first_occupied_unix_ms=0,
bitmap=bytes(282),
)
state.apply_slot_bitmap(snap, ts=50.0)
assert "A" in state.slot_occupancy
occ = state.slot_occupancy["A"]
assert occ["occupied_count"] == 100
assert occ["slots_total"] == 2250
assert occ["occupied_fraction"] == 100 / 2250
# snapshot_slots exposes everything for REST
snap_dict = state.snapshot_slots()
assert snap_dict["occupancy"]["A"]["occupied_count"] == 100
def test_slot_detail_stores_entries():
state = _make_state()
detail = SlotDetail(
channel="B",
utc_minute=1,
slot0_unix_ms=60000,
entries=[SlotLevel(slot=10, level_db=-70.0), SlotLevel(slot=2200, level_db=-80.0)],
)
state.apply_slot_detail(detail, ts=60.0)
stored = state.slot_detail["B"]
assert len(stored["entries"]) == 2
assert stored["entries"][0] == {"slot": 10, "level_db": -70.0}
def test_signal_event_creates_target_with_signal_fields():
state = _make_state()
batch = SignalEventBatch(
channel="A",
events=[
SignalEvent(unix_ms=1_700_000_000_000, slot=42, mmsi=257_000_001, level_db=-75.0),
],
)
state.apply_signal_events(batch, ts=1_700_000_000.0)
assert 257_000_001 in state.targets
tgt = state.targets[257_000_001]
assert tgt.last_signal_db == -75.0
assert tgt.last_signal_slot == 42
assert tgt.last_signal_channel == "A"
# last_seen must be set from signal event if it's newer
assert tgt.last_seen >= 1_700_000_000.0
# to_dict exposes signal block
d = tgt.to_dict()
assert d["signal"] == {
"last_db": -75.0,
"last_ts": 1_700_000_000.0,
"last_slot": 42,
"last_channel": "A",
}
def test_signal_event_older_does_not_overwrite():
state = _make_state()
mmsi = 111_222_333
# first, newer event
state.apply_signal_events(SignalEventBatch(
channel="A",
events=[SignalEvent(unix_ms=2_000_000_000_000, slot=1, mmsi=mmsi, level_db=-60.0)],
), ts=2_000_000_000.0)
# then, older event — must not clobber last_signal_db
state.apply_signal_events(SignalEventBatch(
channel="B",
events=[SignalEvent(unix_ms=1_000_000_000_000, slot=99, mmsi=mmsi, level_db=-90.0)],
), ts=2_000_000_001.0)
tgt = state.targets[mmsi]
assert tgt.last_signal_db == -60.0
assert tgt.last_signal_slot == 1
assert tgt.last_signal_channel == "A"