fix(ptica): нечисловое значение от OSRM/ORS больше не даёт 500 вместо деградации (#2464) #2944

Merged
bot-backend merged 1 commit from fix/2464-routing-value-coercion into main 2026-08-19 17:20:29 +00:00
Collaborator

Один и тот же пропуск в двух клиентах маршрутизации

Оба файла последовательно переводят любую кривизну ответа в доменную ошибку — 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-сбой»

Автор рассуждал ровно про этот класс отказа и закрыл им проверку длины. К самой конверсии тот же принцип просто не применили.

Проверка

тест origin/main
OSRM: строка в distances красный: ValueError: could not convert string to float: 'не число'
OSRM: dict в distances красный: TypeError: float() argument must be a string or a real number, not 'dict'
ORS: строка в durations красный: ValueError
ORS: список в durations красный: TypeError: ... not 'list'

Проверены оба типа исключения, а не только ValueError: except 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, а не до.

Почему это не теория

Флаг use_osrm_distances включён на проде (#39 A3), маршрутизация участвует в каждом /analyze с POI. Ответ портится не «если сервер сойдёт с ума», а при любом рассогласовании версии OSRM-контейнера с ожидаемой формой ответа — ровно тот случай, ради которого весь остальной файл написан оборонительно.

Refs #2464

## Один и тот же пропуск в двух клиентах маршрутизации Оба файла последовательно переводят **любую** кривизну ответа в доменную ошибку — `OsrmLocalUnavailableError` / `OrsUnavailableError`, — потому что вызывающие ловят только её и уходят на прямолинейный fallback (`parcels.py:399`, `poi_score.py:348`). Конверсия значения была единственным исключением: ```python 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-сбой» Автор рассуждал ровно про этот класс отказа и закрыл им проверку длины. К самой конверсии тот же принцип просто не применили. ## Проверка | тест | `origin/main` | |---|---| | OSRM: строка в `distances` | **красный**: `ValueError: could not convert string to float: 'не число'` | | OSRM: dict в `distances` | **красный**: `TypeError: float() argument must be a string or a real number, not 'dict'` | | ORS: строка в `durations` | **красный**: `ValueError` | | ORS: список в `durations` | **красный**: `TypeError: ... not 'list'` | Проверены **оба** типа исключения, а не только `ValueError`: `except 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, а не до. ## Почему это не теория Флаг `use_osrm_distances` включён на проде (#39 A3), маршрутизация участвует в каждом `/analyze` с POI. Ответ портится не «если сервер сойдёт с ума», а при любом рассогласовании версии OSRM-контейнера с ожидаемой формой ответа — ровно тот случай, ради которого весь остальной файл написан оборонительно. Refs #2464
bot-backend added 1 commit 2026-08-19 16:59:01 +00:00
fix(ptica): нечисловое значение от OSRM/ORS больше не даёт 500 вместо деградации (#2464)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 2m15s
CI / backend-tests (pull_request) Successful in 17m15s
05841b6825
Оба клиента маршрутизации переводят ЛЮБУЮ кривизну ответа в доменную ошибку
(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.
bot-backend merged commit 56868f2bde into main 2026-08-19 17:20:29 +00:00
bot-backend deleted branch fix/2464-routing-value-coercion 2026-08-19 17:20:29 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#2944
No description provided.