land_reservation: parser/ingest hardening ПЕРЕД первым реальным прогоном (#1118 follow-up) #1122

Closed
opened 2026-06-07 10:15:33 +00:00 by bot-reviewer · 1 comment
Collaborator

Follow-up из PR #1118 (review findings). Scaffold смерджен, но ingest_reservations инертен на проде (нет beat, seed-URL = placeholder). Эти правки сделать до первого реального прогона ingest (т.е. до/вместе с уточнением seed-URL).

1. ReDoS в _RE_ACT_NUMBER (ВАЖНО) — page_reservation_parser.py:31

Паттерн №\s*(\d+[\s\-–—]?(?:[А-Яа-яA-Za-z]+[\-–—]?)*(?:ПП|ПА|РП|ПГ|ГП|МО))\b — вложенный квантификатор (?:…+…?)* + suffix-альтернация → экспоненциальный backtracking (~2.2s @ 25 симв.) на № <цифры>, за которыми нет валидного суффикса (типично в преамбуле PDF). Сейчас смягчено тем, что .search возвращает первое совпадение в шапке.
Fix: сделать разделитель между токенами обязательным (убрать неоднозначность), напр. №\s*(\d+(?:[\s\-–—][А-Яа-яA-Za-z]+)*[\s\-–—]?(?:ПП|ПА|РП|ПГ|ГП|МО))\b; ИЛИ ограничить поиск первыми ~500 симв. (номер акта всегда в шапке). Добавить тест с длинной строкой без суффикса (timeout-guard).

2. NULL-идемпотентность UPSERT — reservation_ingest.py + миграция

UNIQUE(cad_num, act_number) не дедуплицирует строки с act_number IS NULL (NULL≠NULL) → дубли при ре-ingest документа, где номер акта не распарсился.
Fix: новая миграция 137_*.sql — partial unique index CREATE UNIQUE INDEX IF NOT EXISTS uq_land_reservation_cad_null_act ON land_reservation (cad_num) WHERE act_number IS NULL; (или act_number NOT NULL DEFAULT '' sentinel).

3. Russian-CA bundle вместо verify=Falsereservation_ingest.py:_fetch_pdf

verify=False приемлем для MVP (публичный read-only PDF, без креды), но в проде = MITM-поверхность. Прецедент в репо: ekburg_permits.py/pzz_loader.py тоже verify=False.
Fix (репо-широкий, low-prio): единый env RUSSIAN_CA_BUNDLE для всех клиентов на росс-УЦ (геопортал/pravo.gov66/екатеринбург.рф).

4. _MONTH_MAP insertion-order dependency — page_reservation_parser.py:47

"март" обязан идти перед "ма" (startswith-матчинг): "марта".startswith("ма")==True. Сейчас порядок верный, но хрупко к авто-сортировке dict.
Fix: явные полные стемы или tuple-of-pairs с однозначными ключами.

5. Врезка в analyze + vault (из самого PR #1118)

  • Врезать parcel_reservations (join к cad_parcels.geom) в build_ird_analyze_block — намеренно отложено в #1118 во избежание конфликта по ird_analyze.py (теперь #1115 смерджен, конфликта нет).
  • Уточнить _SEED_DOCS[0]["url"] (реальный PDF 509-ПП на pravo.gov66.ru), сверить purpose-эвристику на живом PDF.
  • Vault-запись (CLAUDE.md rule 6): решение «PDF-parse т.к. нет spatial-источника» + grain таблицы land_reservation.

Acceptance

  • ReDoS-fix + timeout-тест; partial-NULL-index миграция; analyze-врезка с тестом; seed-URL уточнён; vault-запись создана.

Refs: #1118 #1062 #1091 #1067.

Follow-up из PR #1118 (review findings). Scaffold смерджен, но `ingest_reservations` **инертен на проде** (нет beat, seed-URL = placeholder). Эти правки сделать **до первого реального прогона** ingest (т.е. до/вместе с уточнением seed-URL). ## 1. ReDoS в `_RE_ACT_NUMBER` (ВАЖНО) — `page_reservation_parser.py:31` Паттерн `№\s*(\d+[\s\-–—]?(?:[А-Яа-яA-Za-z]+[\-–—]?)*(?:ПП|ПА|РП|ПГ|ГП|МО))\b` — вложенный квантификатор `(?:…+…?)*` + suffix-альтернация → экспоненциальный backtracking (~2.2s @ 25 симв.) на `№ <цифры>`, за которыми нет валидного суффикса (типично в преамбуле PDF). Сейчас смягчено тем, что `.search` возвращает первое совпадение в шапке. **Fix:** сделать разделитель между токенами обязательным (убрать неоднозначность), напр. `№\s*(\d+(?:[\s\-–—][А-Яа-яA-Za-z]+)*[\s\-–—]?(?:ПП|ПА|РП|ПГ|ГП|МО))\b`; ИЛИ ограничить поиск первыми ~500 симв. (номер акта всегда в шапке). Добавить тест с длинной строкой без суффикса (timeout-guard). ## 2. NULL-идемпотентность UPSERT — `reservation_ingest.py` + миграция `UNIQUE(cad_num, act_number)` не дедуплицирует строки с `act_number IS NULL` (NULL≠NULL) → дубли при ре-ingest документа, где номер акта не распарсился. **Fix:** новая миграция `137_*.sql` — partial unique index `CREATE UNIQUE INDEX IF NOT EXISTS uq_land_reservation_cad_null_act ON land_reservation (cad_num) WHERE act_number IS NULL;` (или `act_number NOT NULL DEFAULT ''` sentinel). ## 3. Russian-CA bundle вместо `verify=False` — `reservation_ingest.py:_fetch_pdf` `verify=False` приемлем для MVP (публичный read-only PDF, без креды), но в проде = MITM-поверхность. Прецедент в репо: `ekburg_permits.py`/`pzz_loader.py` тоже `verify=False`. **Fix (репо-широкий, low-prio):** единый env `RUSSIAN_CA_BUNDLE` для всех клиентов на росс-УЦ (геопортал/pravo.gov66/екатеринбург.рф). ## 4. `_MONTH_MAP` insertion-order dependency — `page_reservation_parser.py:47` `"март"` обязан идти перед `"ма"` (startswith-матчинг): `"марта".startswith("ма")==True`. Сейчас порядок верный, но хрупко к авто-сортировке dict. **Fix:** явные полные стемы или tuple-of-pairs с однозначными ключами. ## 5. Врезка в analyze + vault (из самого PR #1118) - Врезать `parcel_reservations` (join к `cad_parcels.geom`) в `build_ird_analyze_block` — намеренно отложено в #1118 во избежание конфликта по `ird_analyze.py` (теперь #1115 смерджен, конфликта нет). - Уточнить `_SEED_DOCS[0]["url"]` (реальный PDF 509-ПП на pravo.gov66.ru), сверить purpose-эвристику на живом PDF. - Vault-запись (CLAUDE.md rule 6): решение «PDF-parse т.к. нет spatial-источника» + grain таблицы `land_reservation`. ## Acceptance - ReDoS-fix + timeout-тест; partial-NULL-index миграция; analyze-врезка с тестом; seed-URL уточнён; vault-запись создана. Refs: #1118 #1062 #1091 #1067.
bot-reviewer added the
priority/p2
scope/backend
scrapers
site-finder
ИРД
labels 2026-06-07 10:15:41 +00:00
Collaborator

Реализовано в #1118 (ПАГЕ-парсер изъятия/резервирования → land_reservation), #1145 (enumerate с pravo.gov66.ru), #1062/#1153 (OCR-пайплайн Tesseract rus). Полный пайплайн land_reservation на main.

Закрываю как реализованное в проде.

Реализовано в #1118 (ПАГЕ-парсер изъятия/резервирования → land_reservation), #1145 (enumerate с pravo.gov66.ru), #1062/#1153 (OCR-пайплайн Tesseract rus). Полный пайплайн land_reservation на main. Закрываю как реализованное в проде.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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#1122
No description provided.