summaryrefslogtreecommitdiff
path: root/stacks/daemon/test_reporting_quality.py
blob: 15f0d4da3fcab8fd7fca9b85a6204fa101ed4a2a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#!/usr/bin/env python3
"""Data-quality window — CALLID match, timed passes, configurable min % good."""
from __future__ import annotations

import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))

from reporting_quality import (  # noqa: E402
    DataQualityTracker,
    RxOutcome,
    classify_rx_line,
)

T0 = 1_000_000.0
WIN = 20


def test_classify_good_callid() -> None:
    assert classify_rx_line("[AX25 UI DG1ABC>QST] 73", "QST") == RxOutcome.GOOD


def test_classify_bad_callid() -> None:
    assert classify_rx_line("[AX25 UI DG1ABC>CB-0] 73", "QST") == RxOutcome.BAD


def test_classify_incomplete_ax25() -> None:
    assert classify_rx_line("[AX25 UI DG1ABC>QST", "QST") == RxOutcome.BAD


def test_classify_voice_noise_ignored() -> None:
    assert classify_rx_line("[CRDOP RX soft-crdop] hiss", "QST") == RxOutcome.IGNORE


def test_classify_crdop_ax25_good() -> None:
    line = "[CRDOP AX25 UI CB-0>QST] ping"
    assert classify_rx_line(line, "QST") == RxOutcome.GOOD


def _close_pass(q: DataQualityTracker, index: int, start: float = T0) -> None:
    q.tick(start + (index + 1) * WIN + 1)


def test_pass_window_max_twenty_seconds() -> None:
    q = DataQualityTracker(passes_required=3, min_good_percent=50, pass_window_sec=20)
    q.record(RxOutcome.GOOD, T0 + 1)
    q.tick(T0 + 19)
    assert len(q.passes) == 0
    _close_pass(q, 0)
    assert list(q.passes) == ["good"]


def test_three_passes_fifty_percent() -> None:
    q = DataQualityTracker(passes_required=3, min_good_percent=50, pass_window_sec=20)
    q.record(RxOutcome.GOOD, T0 + 1)
    _close_pass(q, 0)
    q.record(RxOutcome.BAD, T0 + 20 + 1)
    _close_pass(q, 1)
    assert q.data_error_valid(reporting_enabled=True) is False
    q.record(RxOutcome.GOOD, T0 + 40 + 1)
    _close_pass(q, 2)
    assert q.data_error_valid(reporting_enabled=True) is True


def test_three_passes_below_fifty_percent() -> None:
    q = DataQualityTracker(passes_required=3, min_good_percent=50, pass_window_sec=20)
    q.record(RxOutcome.BAD, T0 + 1)
    _close_pass(q, 0)
    q.record(RxOutcome.BAD, T0 + 21)
    _close_pass(q, 1)
    q.record(RxOutcome.GOOD, T0 + 41)
    _close_pass(q, 2)
    assert q.data_error_valid(reporting_enabled=True) is False


def test_ignore_does_not_consume_pass() -> None:
    q = DataQualityTracker(passes_required=3, min_good_percent=50, pass_window_sec=20)
    q.record(RxOutcome.IGNORE, T0 + 1)
    _close_pass(q, 0)
    assert len(q.passes) == 0
    q.record(RxOutcome.GOOD, T0 + 21)
    _close_pass(q, 1)
    q.record(RxOutcome.GOOD, T0 + 41)
    _close_pass(q, 2)
    q.record(RxOutcome.GOOD, T0 + 61)
    _close_pass(q, 3)
    assert q.data_error_valid(reporting_enabled=True) is True
    assert q.voice_activity is True


def test_silent_window_counts_as_bad() -> None:
    q = DataQualityTracker(passes_required=1, min_good_percent=50, pass_window_sec=20)
    q.current.started_at = T0
    _close_pass(q, 0)
    assert list(q.passes) == ["bad"]


def test_max_cycle_seconds() -> None:
    q = DataQualityTracker(passes_required=3, pass_window_sec=20)
    assert q.max_cycle_seconds() == 60


def test_voice_valid_on_activity() -> None:
    q = DataQualityTracker()
    q.record(RxOutcome.IGNORE, T0 + 1)
    assert q.voice_signal_valid(reporting_enabled=True, link_healthy=True) is True


if __name__ == "__main__":
    test_classify_good_callid()
    test_classify_bad_callid()
    test_classify_incomplete_ax25()
    test_classify_voice_noise_ignored()
    test_classify_crdop_ax25_good()
    test_pass_window_max_twenty_seconds()
    test_three_passes_fifty_percent()
    test_three_passes_below_fifty_percent()
    test_ignore_does_not_consume_pass()
    test_silent_window_counts_as_bad()
    test_max_cycle_seconds()
    test_voice_valid_on_activity()
    print("test_reporting_quality: ok")
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com