144 lines
5.0 KiB
Python
144 lines
5.0 KiB
Python
"""
|
||
Тесты единого словаря типов нарушений.
|
||
|
||
Словарь обслуживает три места сразу: инференс (коды в отчёте), SR-отчёт
|
||
(числовые коды) и веб-интерфейс (подписи). Тесты фиксируют, что все коды
|
||
описаны, а устаревшие значения внешних источников приводятся к канону.
|
||
"""
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
|
||
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
||
|
||
from src.dxa import excel_labels # noqa: E402
|
||
from src.dxa.violations import ( # noqa: E402
|
||
ARTIFACT,
|
||
AXIS_DEVIATION,
|
||
INCOMPLETE_ANATOMY,
|
||
LABELING_ERROR,
|
||
LEGACY_ALIASES,
|
||
MOTION,
|
||
POSITIONING,
|
||
ROI_INCORRECT,
|
||
ROTATION,
|
||
TABLE_TYPES,
|
||
UNSPECIFIED,
|
||
VIOLATION_LABELS,
|
||
VIOLATION_NOTES,
|
||
VIOLATION_SCOPE,
|
||
VIOLATION_SR_CODES,
|
||
VIOLATION_TYPES,
|
||
canon_type,
|
||
catalogue,
|
||
label,
|
||
reason,
|
||
sr_code,
|
||
)
|
||
|
||
|
||
class TestCanonType:
|
||
"""Устаревшие значения из внешних источников приводятся к канону."""
|
||
|
||
@pytest.mark.parametrize("legacy, expected", [
|
||
("artifact_motion", MOTION),
|
||
("artifact_other", ARTIFACT),
|
||
("quality_violation_detected", UNSPECIFIED),
|
||
("roi_error", ROI_INCORRECT),
|
||
("position_error", POSITIONING),
|
||
("incomplete_view", INCOMPLETE_ANATOMY),
|
||
])
|
||
def test_legacy_aliases(self, legacy, expected):
|
||
assert canon_type(legacy) == expected
|
||
|
||
@pytest.mark.parametrize("value", ["", None, "correct", "none"])
|
||
def test_empty_values(self, value):
|
||
assert canon_type(value) == ""
|
||
|
||
def test_canonical_codes_pass_through(self):
|
||
for code in VIOLATION_TYPES:
|
||
assert canon_type(code) == code
|
||
|
||
def test_case_and_spaces_are_normalised(self):
|
||
assert canon_type(" ROTATION ") == ROTATION
|
||
|
||
def test_unknown_value_falls_back_to_unspecified(self):
|
||
assert canon_type("что_то_новое") == UNSPECIFIED
|
||
|
||
def test_every_alias_target_is_a_known_code(self):
|
||
for target in LEGACY_ALIASES.values():
|
||
assert target == "" or target in VIOLATION_TYPES
|
||
|
||
|
||
class TestLabels:
|
||
def test_label_for_known_code(self):
|
||
assert label(ROTATION) == VIOLATION_LABELS[ROTATION]
|
||
|
||
def test_label_for_empty_code_uses_default(self):
|
||
assert label("") == "Корректно"
|
||
assert label("", empty="—") == "—"
|
||
|
||
def test_label_accepts_legacy_value(self):
|
||
assert label("artifact_motion") == VIOLATION_LABELS[MOTION]
|
||
|
||
def test_reason_for_unknown_code_is_unspecified_note(self):
|
||
assert reason("нечто") == VIOLATION_NOTES[UNSPECIFIED]
|
||
|
||
def test_reason_for_empty_code_is_empty(self):
|
||
assert reason("") == ""
|
||
|
||
|
||
class TestSrCode:
|
||
def test_acceptable_code_for_good_image(self):
|
||
numeric, english, completion = sr_code("")
|
||
assert numeric == "113001" and completion == "FINAL"
|
||
assert "acceptable" in english
|
||
|
||
def test_warning_code_for_violation(self):
|
||
numeric, english, completion = sr_code(ROTATION)
|
||
assert numeric == VIOLATION_SR_CODES[ROTATION][0] and completion == "WARNING"
|
||
|
||
def test_unknown_code_falls_back_to_unspecified(self):
|
||
assert sr_code("нечто") == (*VIOLATION_SR_CODES[UNSPECIFIED], "WARNING")
|
||
|
||
|
||
class TestCatalogue:
|
||
def test_every_code_is_described(self):
|
||
entries = catalogue()
|
||
assert [e["code"] for e in entries] == list(VIOLATION_TYPES)
|
||
for entry in entries:
|
||
assert entry["label"] and entry["note"]
|
||
assert entry["scope"] in ("spine", "hip", "any")
|
||
assert entry["source"] in ("expert_table", "condition_doctor", "system")
|
||
|
||
def test_table_codes_are_marked_as_expert_table(self):
|
||
by_code = {e["code"]: e for e in catalogue()}
|
||
for code in TABLE_TYPES:
|
||
assert by_code[code]["source"] == "expert_table"
|
||
assert by_code[UNSPECIFIED]["source"] == "system"
|
||
|
||
def test_every_code_has_sr_code(self):
|
||
for code in VIOLATION_TYPES:
|
||
assert code in VIOLATION_SR_CODES
|
||
|
||
def test_every_code_has_label_and_note(self):
|
||
for code in VIOLATION_TYPES:
|
||
assert code in VIOLATION_LABELS
|
||
assert code in VIOLATION_NOTES
|
||
assert code in VIOLATION_SCOPE
|
||
|
||
|
||
class TestAgreementWithAnnotationTable:
|
||
"""Словарь таблицы и общий словарь не должны разойтись."""
|
||
|
||
def test_table_types_match_table_criteria(self):
|
||
expected = set(excel_labels.SPINE_CRITERIA) | set(excel_labels.HIP_CRITERIA)
|
||
assert set(TABLE_TYPES) == expected
|
||
|
||
def test_excel_labels_reexports_same_codes(self):
|
||
assert excel_labels.UNSPECIFIED == UNSPECIFIED
|
||
assert excel_labels.ARTIFACT == ARTIFACT
|
||
assert excel_labels.AXIS_DEVIATION == AXIS_DEVIATION
|
||
assert set(excel_labels.VIOLATION_TYPES) >= set(TABLE_TYPES) | {UNSPECIFIED}
|