""" Тесты единого словаря типов нарушений. Словарь обслуживает три места сразу: инференс (коды в отчёте), 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}