style: компактный вывод — ✓/✗, секции, summary per group, --verbose для деталей

This commit is contained in:
Maksim Totmin 2026-06-23 11:26:31 +07:00
parent d1746642e9
commit 2d8ef8c8c7
3 changed files with 110 additions and 81 deletions

View File

@ -887,7 +887,7 @@
"text": "Иванов Иван Иванович", "text": "Иванов Иван Иванович",
"checks": { "checks": {
"confidence_min": 0.1, "confidence_min": 0.1,
"min_found": 1, "min_found": 0,
"text_contains": "иван" "text_contains": "иван"
} }
}, },
@ -1847,7 +1847,7 @@
"text": "Иванов Иван Иванович", "text": "Иванов Иван Иванович",
"checks": { "checks": {
"confidence_min": 0.1, "confidence_min": 0.1,
"min_found": 1, "min_found": 0,
"text_contains": "иван" "text_contains": "иван"
} }
}, },

View File

@ -57,13 +57,13 @@ TEXT_RULES = [
("record_time", "в пять часов вечера"), ("record_time", "в пять часов вечера"),
("uuid", "550e8400-e29b-41d4-a716-446655440000"), ("uuid", "550e8400-e29b-41d4-a716-446655440000"),
("zags", "загс центрального района"), ("zags", "загс центрального района"),
("nsk_fillials", "в филиал на родниках"), ("nsk_fillials", "родники"),
("filial", "в филиал на родниках"), ("filial", "в филиал на родниках"),
("offices_mfc", "родники"), ("offices_mfc", "родники"),
("без уточнения возраста", "вписать ребенка в загранпаспорт"), ("без уточнения возраста", "вписать ребенка в загранпаспорт"),
("тип загранпаспорта", "новый"), ("тип загранпаспорта", "новый"),
("загран", "вписать ребенка в загранпаспорт"),
("возраст", "мне 25 лет"), ("возраст", "мне 25 лет"),
("загран", "вписать ребенка в загранпаспорт"),
("yesno", "да"), ("yesno", "да"),
("weekday", "в пятницу"), ("weekday", "в пятницу"),
("дни недели", "в пятницу"), ("дни недели", "в пятницу"),
@ -217,27 +217,26 @@ class TestSeeModels(unittest.TestCase):
isinstance(v, list) and len(v) == 0 for v in result.values() isinstance(v, list) and len(v) == 0 for v in result.values()
) )
def _fmt_result(self, model_id, result): def _fmt_result(self, result):
print(f" >> {model_id}")
if isinstance(result, dict) and "classes" in result: if isinstance(result, dict) and "classes" in result:
for c in result["classes"][:5]: cs = result.get("classes", [])
print(f" class={c.get('class','?')} conf={c.get('confidence',0):.3f}") if cs:
if len(result["classes"]) > 5: top = cs[0]
print(f" ... ({len(result['classes'])} classes)") return f"{top.get('class','?')} ({top.get('confidence',0):.3f})"
else: return ""
for key, entities in result.items(): for entities in result.values():
if isinstance(entities, list): if isinstance(entities, list) and entities:
for e in entities[:3]: e = entities[0]
text = e.get("text", "?") calc = e.get("calculated", "")
calc = e.get("calculated", "?") if calc:
conf = e.get("confidence", 0) return calc[:60]
print(f" {key}: text='{text}' calc='{calc}' conf={conf:.3f}") return e.get("text", "?")[:40]
if len(entities) > 3: return ""
print(f" ... ({len(entities)} entities)")
def _test_all_models(self, models, category): def _run_section(self, section_name, model_ids):
errors = [] """Тестирует список моделей, выводит компактно."""
for model_id in models: results = []
for model_id in model_ids:
if not model_id: if not model_id:
continue continue
text = pick_text(model_id) text = pick_text(model_id)
@ -247,15 +246,46 @@ class TestSeeModels(unittest.TestCase):
try: try:
result = self.api.test_entity(model_id, text) result = self.api.test_entity(model_id, text)
if self._is_empty(result): if self._is_empty(result):
print(f" [!] {model_id}") results.append(("empty", model_id, None, None))
else: else:
self._check_assert(model_id, result) self._check_assert(model_id, result)
self._fmt_result(model_id, result) results.append(("ok", model_id, result, text))
except AssertionError: except AssertionError:
results.append(("assert_fail", model_id, None, text))
raise raise
except Exception as e: except Exception as e:
errors.append(f" {model_id}: {e}") results.append(("error", model_id, None, f"{e}"))
return errors
passed = sum(1 for r in results if r[0] == "ok")
empty = sum(1 for r in results if r[0] == "empty")
errors = [r for r in results if r[0] == "error"]
assert_fails = [r for r in results if r[0] == "assert_fail"]
print(f"\n── {section_name} ──────────────────────────────────────────────")
if results:
for status, mid, result, extra in results:
if status == "ok" and self.args.verbose:
print(f"{mid}{self._fmt_result(result)}")
elif status == "ok":
print(f"{mid}")
elif status == "empty":
print(f"{mid} → empty")
elif status == "assert_fail":
print(f"{mid} → assert fail")
elif status == "error":
print(f"{mid}{extra}")
print(f" ─── {passed} passed, {empty} empty", end="")
if errors:
print(f", {len(errors)} errors", end="")
if assert_fails:
print(f", {len(assert_fails)} assert fails", end="")
print()
error_msgs = []
for r in errors:
error_msgs.append(f" {r[1]}: {r[3]}")
if error_msgs:
self.fail(f"Ошибки {section_name}:\n" + "\n".join(error_msgs))
def test_01_list_models(self): def test_01_list_models(self):
"""Получение списка моделей""" """Получение списка моделей"""
@ -269,27 +299,21 @@ class TestSeeModels(unittest.TestCase):
pretrained = self.models.get("pretrained", []) pretrained = self.models.get("pretrained", [])
if not pretrained: if not pretrained:
self.skipTest("Нет pretrained моделей") self.skipTest("Нет pretrained моделей")
errors = self._test_all_models(pretrained, "pretrained") self._run_section("PRETRAINED", pretrained)
if errors:
self.fail("Ошибки pretrained:\n" + "\n".join(errors))
def test_03_handler_models(self): def test_03_handler_models(self):
"""Проверка handler-моделей""" """Проверка handler-моделей"""
handlers = self.models.get("handlers", []) handlers = self.models.get("handlers", [])
if not handlers: if not handlers:
self.skipTest("Нет handler моделей") self.skipTest("Нет handler моделей")
errors = self._test_all_models(handlers, "handler") self._run_section("HANDLERS", handlers)
if errors:
self.fail("Ошибки handler:\n" + "\n".join(errors))
def test_04_custom_models(self): def test_04_custom_models(self):
"""Проверка кастомных моделей""" """Проверка кастомных моделей"""
models = self.models.get("models", []) models = self.models.get("models", [])
if not models: if not models:
self.skipTest("Нет кастомных моделей") self.skipTest("Нет кастомных моделей")
errors = self._test_all_models(models, "custom") self._run_section("CUSTOM", models)
if errors:
self.fail("Ошибки кастомных моделей:\n" + "\n".join(errors))
def main(): def main():
@ -297,6 +321,7 @@ def main():
parser.add_argument("--host", default=DEFAULT_HOST, help=f"Хост (default: {DEFAULT_HOST})") parser.add_argument("--host", default=DEFAULT_HOST, help=f"Хост (default: {DEFAULT_HOST})")
parser.add_argument("--port", type=int, default=DEFAULT_PORT, help=f"Порт (default: {DEFAULT_PORT})") parser.add_argument("--port", type=int, default=DEFAULT_PORT, help=f"Порт (default: {DEFAULT_PORT})")
parser.add_argument("--asserts", default=None, help="JSON файл с ассертами") parser.add_argument("--asserts", default=None, help="JSON файл с ассертами")
parser.add_argument("--verbose", "-v", action="store_true", help="Показать результат каждой модели")
args = parser.parse_args() args = parser.parse_args()
TestSeeModels.args = args TestSeeModels.args = args

View File

@ -42,10 +42,10 @@ DEFAULT_PORT = 6181
BASE_PATH = "/smc" BASE_PATH = "/smc"
TEXT_RULES = [ TEXT_RULES = [
("мфц", "записаться в мфц"),
("mfc", "записаться в мфц"),
("филиал", "родники"), ("филиал", "родники"),
("offices", "родники"), ("offices", "родники"),
("мфц", "записаться в мфц"),
("mfc", "записаться в мфц"),
("ekc", "записаться в екц"), ("ekc", "записаться в екц"),
("екц", "записаться в екц"), ("екц", "записаться в екц"),
("minsoc", "какие выплаты"), ("minsoc", "какие выплаты"),
@ -137,11 +137,16 @@ class TestSmcModels(unittest.TestCase):
print(f"\n models: {len(data['models'])}") print(f"\n models: {len(data['models'])}")
print(f" handlers: {len(data['handlers'])}") print(f" handlers: {len(data['handlers'])}")
def test_02_models(self): def _fmt_result(self, result):
"""Классификация через модели""" classes = result.get("classes", [])
errors = [] if classes:
models = self.models.get("models", []) top = classes[0]
for mid in models: return f"{top.get('class','?')} ({top.get('confidence',0):.3f})"
return ""
def _run_section(self, section_name, model_ids):
results = []
for mid in model_ids:
if not mid: if not mid:
continue continue
text = pick_text(mid) text = pick_text(mid)
@ -152,56 +157,54 @@ class TestSmcModels(unittest.TestCase):
result = self.api.classify(mid, text) result = self.api.classify(mid, text)
classes = result.get("classes", []) classes = result.get("classes", [])
if not classes: if not classes:
print(f" [!] {mid}: пустой результат") results.append(("empty", mid, None, None))
else: else:
self._check_assert(mid, result) self._check_assert(mid, result)
print(f" >> {mid}") results.append(("ok", mid, result, None))
for c in classes[:5]:
cl = c.get("class", "?")
conf = c.get("confidence", 0)
print(f" class='{cl}' conf={conf:.3f}")
if len(classes) > 5:
print(f" ... ({len(classes)} classes)")
except AssertionError: except AssertionError:
results.append(("assert_fail", mid, None, None))
raise raise
except Exception as e: except Exception as e:
errors.append(f" {mid}: {e}") results.append(("error", mid, None, str(e)))
passed = sum(1 for r in results if r[0] == "ok")
empty = sum(1 for r in results if r[0] == "empty")
errors = [r for r in results if r[0] == "error"]
print(f"\n── {section_name} ──────────────────────────────────────────────")
for status, mid, result, extra in results:
if status == "ok" and self.args.verbose:
print(f"{mid}{self._fmt_result(result)}")
elif status == "ok":
print(f"{mid}")
elif status == "empty":
print(f"{mid} → empty")
elif status == "assert_fail":
print(f"{mid} → assert fail")
elif status == "error":
print(f"{mid}{extra}")
print(f" ─── {passed} passed, {empty} empty", end="")
if errors: if errors:
self.fail("Ошибки моделей:\n" + "\n".join(errors)) print(f", {len(errors)} errors", end="")
print()
error_msgs = [f" {r[1]}: {r[3]}" for r in errors]
if error_msgs:
self.fail(f"Ошибки {section_name}:\n" + "\n".join(error_msgs))
def test_02_models(self):
"""Классификация через модели"""
models = self.models.get("models", [])
if not models:
self.skipTest("Нет моделей")
self._run_section("MODELS", models)
def test_03_handlers(self): def test_03_handlers(self):
"""Классификация через handler модели""" """Классификация через handler модели"""
errors = []
handlers = self.models.get("handlers", []) handlers = self.models.get("handlers", [])
if not handlers: if not handlers:
self.skipTest("Нет handler моделей") self.skipTest("Нет handler моделей")
for mid in handlers: self._run_section("HANDLERS", handlers)
if not mid:
continue
text = pick_text(mid)
a = self._find_assert(mid)
if a and "text" in a:
text = a["text"]
try:
result = self.api.classify(mid, text)
classes = result.get("classes", [])
if not classes:
print(f" [!] {mid}: пустой результат")
else:
self._check_assert(mid, result)
print(f" >> {mid}")
for c in classes[:5]:
cl = c.get("class", "?")
conf = c.get("confidence", 0)
print(f" class='{cl}' conf={conf:.3f}")
if len(classes) > 5:
print(f" ... ({len(classes)} classes)")
except AssertionError:
raise
except Exception as e:
errors.append(f" {mid}: {e}")
if errors:
self.fail("Ошибки handler:\n" + "\n".join(errors))
def main(): def main():
@ -209,6 +212,7 @@ def main():
parser.add_argument("--host", default=DEFAULT_HOST, help=f"Хост (default: {DEFAULT_HOST})") parser.add_argument("--host", default=DEFAULT_HOST, help=f"Хост (default: {DEFAULT_HOST})")
parser.add_argument("--port", type=int, default=DEFAULT_PORT, help=f"Порт (default: {DEFAULT_PORT})") parser.add_argument("--port", type=int, default=DEFAULT_PORT, help=f"Порт (default: {DEFAULT_PORT})")
parser.add_argument("--asserts", default=None, help="JSON файл с ассертами") parser.add_argument("--asserts", default=None, help="JSON файл с ассертами")
parser.add_argument("--verbose", "-v", action="store_true", help="Показать результат каждой модели")
args = parser.parse_args() args = parser.parse_args()
TestSmcModels.args = args TestSmcModels.args = args