fetch_via_rosreestr2coord глотает все исключения → None: WAF-блоки не детектируются, цель помечается 'done' с 0 features, run-статус лжёт #1648

Closed
opened 2026-06-16 18:46:55 +00:00 by bot-backend · 0 comments
Collaborator

Severity: high · Категория: silent-except-data-loss-false-run-status · Файл: backend/app/services/scrapers/nspd_lite.py:186-188

Файл: backend/app/services/scrapers/nspd_lite.py:186-188. to_geojson_poly(...) обёрнут в except Exception as e: logger.warning(...); return None — любая ошибка (WAF 403/429, сеть, таймаут, парс-ошибка rosreestr2coord) превращается в None, неотличимый от легитимного 'участок не найден'. Это единственный путь NSPD-geo fetch в проде (workers/tasks/nspd_geo.py:432-440, urllib-ветка fetch_geoportal удалена).

Импакт: воркер process_nspd_geo_job построен вокруг NspdLiteWafError (nspd_geo.py:464 — экспоненциальный backoff, инкремент waf_blocked_count, пауза job'а после 8 подряд WAF). Но функция это исключение никогда не поднимает → вся WAF-ветка мёртвый код. При WAF/429 возвращается None → payload['data']['features'] после фильтра [f for f in ... if f] (440) = [] → исполняется happy-path (446-462): consecutive_waf=0, _persist_target вернёт 0, target помечается status='done' (453), done+=1, n_waf не растёт. SELECT фильтрует status='pending' (415) → 'done'-цель не переподхватывается, ретрая нет. _finish_job(...,'done') (590) + лог 'N ok / 0 WAF blocks' (596). Итог: при бане WAF массово теряются геометрии участков/кварталов/зданий БЕЗ ретрая и БЕЗ failed-пометки, а run-статус и waf_blocked_count врут об успехе.

Фикс: пробрасывать WAF/HTTP-ошибки как NspdLiteWafError/NspdLiteError из fetch_via_rosreestr2coord (различать transient-бан от 'не найдено'), чтобы существующая ветка except NspdLiteWafError срабатывала; либо в воркере не помечать target 'done' при пустом фиче-листе вследствие ошибки. Также пересмотреть generic except (NspdLiteError, Exception) (nspd_geo.py:550), который сейчас единственный, но недостижим из-за None-возврата.

Почему баг: Воркер process_nspd_geo_job построен вокруг исключения NspdLiteWafError: ветка except NspdLiteWafError (nspd_geo.py:464) делает экспоненциальный backoff, инкремент waf_blocked_count и паузу job'а после 8 подряд WAF. Но fetch_via_rosreestr2coord никогда не поднимает это (и вообще ничего) — значит ВСЯ WAF-логика воркера мёртвый код. При WAF/429 функция вернёт None, в payload['data']['features'] после фильтра [f for f in ... if f] останется [], _persist_target вернёт 0, цель помечается status='done' (nspd_geo.py:453), n_waf не растёт, backoff не срабатывает, job завершается _finish_job(...,'done') с отчётом 'N ok / 0 WAF blocks'. Итог: при бане WAF массово теряются геометрии участков/кварталов/зданий БЕЗ ретрая и БЕЗ failed-пометки (target уже 'done' → повторный прогон его не подхватит, status='pending' фильтр в SELECT его пропустит), а run-статус и waf_blocked_count врут об успехе. Это и потеря данных, и ложь в отчёте, и идемпотентность run-статуса нарушена.

Как чинить: Не глушить всё в None. Пробросить транзиентные/WAF ошибки наружу как NspdLiteWafError/NspdLiteError (различать по тексту/типу исключения rosreestr2coord, напр. HTTP 403/429), чтобы воркерная ветка backoff/pause реально работала; возвращать None ТОЛЬКО для подтверждённого 'не найдено'. Либо в nspd_geo.py отличать None-как-ошибку от None-как-not-found (например, помечать цель 'failed'/'pending' при None, а не 'done').


deep-audit backend v2 (2-й проход, после PR #1543) · verify+harden, confidence 0.9 · unit B35

**Severity:** high · **Категория:** silent-except-data-loss-false-run-status · **Файл:** `backend/app/services/scrapers/nspd_lite.py:186-188` Файл: backend/app/services/scrapers/nspd_lite.py:186-188. `to_geojson_poly(...)` обёрнут в `except Exception as e: logger.warning(...); return None` — любая ошибка (WAF 403/429, сеть, таймаут, парс-ошибка rosreestr2coord) превращается в None, неотличимый от легитимного 'участок не найден'. Это единственный путь NSPD-geo fetch в проде (workers/tasks/nspd_geo.py:432-440, urllib-ветка fetch_geoportal удалена). Импакт: воркер process_nspd_geo_job построен вокруг NspdLiteWafError (nspd_geo.py:464 — экспоненциальный backoff, инкремент waf_blocked_count, пауза job'а после 8 подряд WAF). Но функция это исключение никогда не поднимает → вся WAF-ветка мёртвый код. При WAF/429 возвращается None → payload['data']['features'] после фильтра `[f for f in ... if f]` (440) = [] → исполняется happy-path (446-462): consecutive_waf=0, _persist_target вернёт 0, target помечается status='done' (453), done+=1, n_waf не растёт. SELECT фильтрует status='pending' (415) → 'done'-цель не переподхватывается, ретрая нет. _finish_job(...,'done') (590) + лог 'N ok / 0 WAF blocks' (596). Итог: при бане WAF массово теряются геометрии участков/кварталов/зданий БЕЗ ретрая и БЕЗ failed-пометки, а run-статус и waf_blocked_count врут об успехе. Фикс: пробрасывать WAF/HTTP-ошибки как NspdLiteWafError/NspdLiteError из fetch_via_rosreestr2coord (различать transient-бан от 'не найдено'), чтобы существующая ветка except NspdLiteWafError срабатывала; либо в воркере не помечать target 'done' при пустом фиче-листе вследствие ошибки. Также пересмотреть generic `except (NspdLiteError, Exception)` (nspd_geo.py:550), который сейчас единственный, но недостижим из-за None-возврата. **Почему баг:** Воркер process_nspd_geo_job построен вокруг исключения NspdLiteWafError: ветка `except NspdLiteWafError` (nspd_geo.py:464) делает экспоненциальный backoff, инкремент waf_blocked_count и паузу job'а после 8 подряд WAF. Но fetch_via_rosreestr2coord никогда не поднимает это (и вообще ничего) — значит ВСЯ WAF-логика воркера мёртвый код. При WAF/429 функция вернёт None, в payload['data']['features'] после фильтра `[f for f in ... if f]` останется [], _persist_target вернёт 0, цель помечается status='done' (nspd_geo.py:453), n_waf не растёт, backoff не срабатывает, job завершается _finish_job(...,'done') с отчётом 'N ok / 0 WAF blocks'. Итог: при бане WAF массово теряются геометрии участков/кварталов/зданий БЕЗ ретрая и БЕЗ failed-пометки (target уже 'done' → повторный прогон его не подхватит, status='pending' фильтр в SELECT его пропустит), а run-статус и waf_blocked_count врут об успехе. Это и потеря данных, и ложь в отчёте, и идемпотентность run-статуса нарушена. **Как чинить:** Не глушить всё в None. Пробросить транзиентные/WAF ошибки наружу как NspdLiteWafError/NspdLiteError (различать по тексту/типу исключения rosreestr2coord, напр. HTTP 403/429), чтобы воркерная ветка backoff/pause реально работала; возвращать None ТОЛЬКО для подтверждённого 'не найдено'. Либо в nspd_geo.py отличать None-как-ошибку от None-как-not-found (например, помечать цель 'failed'/'pending' при None, а не 'done'). --- <sub>deep-audit backend v2 (2-й проход, после PR #1543) · verify+harden, confidence 0.9 · unit B35</sub>
bot-backend added the
week ревью 1
label 2026-06-16 18:46:55 +00:00
Sign in to join this conversation.
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#1648
No description provided.