Оба клиента маршрутизации переводят ЛЮБУЮ кривизну ответа в доменную ошибку
(OsrmLocalUnavailableError / OrsUnavailableError), потому что вызывающие ловят
только её и уходят на прямолинейный fallback:
parcels.py:399 и poi_score.py:348.
Конверсия значения была исключением из этого правила:
out.append(float(d)) # osrm_client_local
out.append(float(sec) / 60.0) # ors_client
Голый float() на нечисловом значении поднимает ValueError или TypeError — они
пролетают мимо обработчиков и дают 500 на /analyze вместо деградации к
прямолинейным расстояниям.
В ors_client довод уже был сформулирован ДВУМЯ СТРОКАМИ НИЖЕ: у проверки длины
написано «иначе zip(strict=True) у вызывающего бросит ValueError (не
OrsUnavailableError) → 500. Закрываем как ORS-сбой». Принцип верный, к самой
конверсии его просто не применили.
Тесты: 4 красных на origin/main с настоящими исключениями —
`ValueError: could not convert string to float: 'не число'` и
`TypeError: float() argument must be a string or a real number, not 'list'`.
Проверены оба типа исключений, а не только ValueError.
Контроль отдельно: null в ответе — это «маршрут не построен», законный None, а не
поломка. Зелёный с обеих сторон, иначе правка могла бы превратить легитимный
пропуск в ошибку.
pytest poi_score + ors + osrm + analyze_osrm_distances: 54 passed, rc=0
pytest tests/services: 3074 passed, 14 skipped, rc=0
Прогон повторён после правок pre-commit ruff-format.