fix(ptica): act_date — дата акта об изъятии, а не первой попавшейся ссылки (#2464) #2981

Merged
bot-backend merged 1 commit from fix/2464-act-date-citation into main 2026-08-20 16:43:28 +00:00
Collaborator

Как нашлось

Не по чтению кода. Смотрел land_reservation и увидел, что 11 строк из 27 делят одну дату 2004-07-06, причём это два разных документа — развязка на Сибирском тракте и улица Энергостроителей, проекты 2020-х. Совпадение даты у несвязанных актов и было признаком, что дата не своя.

Доказано, а не предположено

Все пять исходных PDF загружены с екатеринбург.рф и распознаны тем же трактом, что использует загрузчик (ocr_pdf_text в прод-контейнере). Вот откуда бралась дата:

«Администрация города Екатеринбурга
 — Решение Екатеринбургской городской Думы от 06.07.2004 № 60/1
   «Об утверждении Генерального плана города»;         ← отсюда act_date
 — Приказ Министерства строительства ... от 30.12.2021 № 746-П;
 — Постановление Администрации города Екатеринбурга
   от 27.05.2022 № 1413 «Об утверждении проекта планировки»  ← настоящее основание

_extract_act_date брал первое «от DD.MM.YYYY» во всём тексте. У «Сообщений о планируемом изъятии» первой строкой оснований почти всегда стоит Генплан-2004.

документ было должно быть
развязка Базовый/Комсомольская/Сибирский тракт 2004-07-06 2022-05-27 (№ 1413)
улица Энергостроителей 2004-07-06 2020-02-12 (№ 259)
Павлодарская — Щербакова 2015-09-29 2015-09-29 ✓
Иркутская — Шаумяна 2019-04-19 2019-04-19 ✓
Татищева — Юрия Исламова 2019-06-24 2019-06-24 ✓

Три документа были верны случайно — у них первой датой и оказалось нужное постановление.

Правка

Дата принимается, только если в 120 символах перед ней стоит слово «постановлени». Ссылки-помехи (Решение … Думы, Приказ Министерства) его не содержат.

Окно шире самой фразы (~50 символов), потому что OCR перемешивает колонки таблицы: …Администрации города документами) Екатеринбурга от 19.04.2019….

Нет подходящей даты → None. Дата чужого документа хуже пустоты: по ней нельзя ни отфильтровать актуальные изъятия, ни сверить срок, и она неотличима от настоящей.

Калибровка — на всех пяти сразу, не на одном

правило окно 80 120 160
постановлени 5/5 5/5 5/5
постановлени + администраци 5/5 5/5 5/5

Узкий вариант давал те же 5/5, но ломал законный случай Постановление № 509-ПП — областной акт без слова «администрация», уже закреплённый тестом test_act_date_extracted_from_text. Это и решило выбор: на живых данных широкое условие не хуже, а лишнего не отсекает. Правило, откалиброванное на одном образце, работает только на нём — потому и брал все пять.

Сквозная проверка

Патченный код прогнан по всем пяти распознанным текстам целиком:

izy1 (развязка)          участков=9  act_date=2022-05-27   (было 2004-07-06)
188a5c (Татищева)        участков=7  act_date=2019-06-24
9b9d9a (Энергостроит.)   участков=2  act_date=2020-02-12   (было 2004-07-06)
bf2bb1 (Павлодарская)    участков=2  act_date=2015-09-29
ccd002 (Иркутская)       участков=7  act_date=2019-04-19
                     всего записей = 27

27 — ровно столько же строк, сколько в land_reservation на проде. 11 меняют дату, 16 не двигаются.

Как проверено

  • Двусторонне: против origin/main три теста красные с реальным неверным значением (assert '2004-07-06' == '2020-02-12'), ни одного TypeError: тесты идут через extract_izyatie_records, чья сигнатура одинакова на обеих сторонах, а не через приватный хелпер с новым параметром.
  • Фрагменты в тестах — дословный OCR прод-документов, включая перемешанные колонки.
  • Контроли: приказ Министерства не берётся; верные документы не меняются; без основания — None, а не чужая дата; заголовок разбирается по-прежнему.
  • pytest backend/tests/services/scrapers/ — 312 passed, 6 skipped.

Остаётся открытым

act_number пуст у всех 27 строк: _RE_ACT_NUMBER требует суффикс ПП|ПА|РП|ПГ|ГП|МО, а реальные номера — 1413, 259, 1504, 2687, 863. Не чиню здесь: act_number входит в уникальный ключ (cad_num, act_number) (миграция 189), поэтому его заполнение меняет дедупликацию и требует отдельного разбора.

Часть эпика #2464.

## Как нашлось Не по чтению кода. Смотрел `land_reservation` и увидел, что **11 строк из 27 делят одну дату 2004-07-06**, причём это два *разных* документа — развязка на Сибирском тракте и улица Энергостроителей, проекты 2020-х. Совпадение даты у несвязанных актов и было признаком, что дата не своя. ## Доказано, а не предположено Все пять исходных PDF загружены с екатеринбург.рф и распознаны **тем же трактом, что использует загрузчик** (`ocr_pdf_text` в прод-контейнере). Вот откуда бралась дата: ``` «Администрация города Екатеринбурга — Решение Екатеринбургской городской Думы от 06.07.2004 № 60/1 «Об утверждении Генерального плана города»; ← отсюда act_date — Приказ Министерства строительства ... от 30.12.2021 № 746-П; — Постановление Администрации города Екатеринбурга от 27.05.2022 № 1413 «Об утверждении проекта планировки» ← настоящее основание ``` `_extract_act_date` брал **первое** «от DD.MM.YYYY» во всём тексте. У «Сообщений о планируемом изъятии» первой строкой оснований почти всегда стоит Генплан-2004. | документ | было | должно быть | |---|---|---| | развязка Базовый/Комсомольская/Сибирский тракт | 2004-07-06 | **2022-05-27** (№ 1413) | | улица Энергостроителей | 2004-07-06 | **2020-02-12** (№ 259) | | Павлодарская — Щербакова | 2015-09-29 | 2015-09-29 ✓ | | Иркутская — Шаумяна | 2019-04-19 | 2019-04-19 ✓ | | Татищева — Юрия Исламова | 2019-06-24 | 2019-06-24 ✓ | Три документа были верны случайно — у них первой датой и оказалось нужное постановление. ## Правка Дата принимается, только если в 120 символах перед ней стоит слово «постановлени». Ссылки-помехи (`Решение … Думы`, `Приказ Министерства`) его не содержат. Окно шире самой фразы (~50 символов), потому что OCR перемешивает колонки таблицы: `…Администрации города документами) Екатеринбурга от 19.04.2019…`. Нет подходящей даты → `None`. Дата чужого документа хуже пустоты: по ней нельзя ни отфильтровать актуальные изъятия, ни сверить срок, и она неотличима от настоящей. ## Калибровка — на всех пяти сразу, не на одном | правило | окно 80 | 120 | 160 | |---|---|---|---| | `постановлени` | 5/5 | 5/5 | 5/5 | | `постановлени` + `администраци` | 5/5 | 5/5 | 5/5 | Узкий вариант давал те же 5/5, но ломал законный случай `Постановление № 509-ПП` — областной акт без слова «администрация», уже закреплённый тестом `test_act_date_extracted_from_text`. Это и решило выбор: на живых данных широкое условие не хуже, а лишнего не отсекает. Правило, откалиброванное на одном образце, работает только на нём — потому и брал все пять. ## Сквозная проверка Патченный код прогнан по всем пяти распознанным текстам целиком: ``` izy1 (развязка) участков=9 act_date=2022-05-27 (было 2004-07-06) 188a5c (Татищева) участков=7 act_date=2019-06-24 9b9d9a (Энергостроит.) участков=2 act_date=2020-02-12 (было 2004-07-06) bf2bb1 (Павлодарская) участков=2 act_date=2015-09-29 ccd002 (Иркутская) участков=7 act_date=2019-04-19 всего записей = 27 ``` 27 — ровно столько же строк, сколько в `land_reservation` на проде. 11 меняют дату, 16 не двигаются. ## Как проверено - **Двусторонне:** против `origin/main` три теста красные с реальным неверным значением (`assert '2004-07-06' == '2020-02-12'`), **ни одного TypeError**: тесты идут через `extract_izyatie_records`, чья сигнатура одинакова на обеих сторонах, а не через приватный хелпер с новым параметром. - Фрагменты в тестах — дословный OCR прод-документов, включая перемешанные колонки. - Контроли: приказ Министерства не берётся; верные документы не меняются; без основания — `None`, а не чужая дата; заголовок разбирается по-прежнему. - `pytest backend/tests/services/scrapers/` — 312 passed, 6 skipped. ## Остаётся открытым `act_number` пуст у всех 27 строк: `_RE_ACT_NUMBER` требует суффикс `ПП|ПА|РП|ПГ|ГП|МО`, а реальные номера — `1413`, `259`, `1504`, `2687`, `863`. Не чиню здесь: `act_number` входит в уникальный ключ `(cad_num, act_number)` (миграция 189), поэтому его заполнение меняет дедупликацию и требует отдельного разбора. Часть эпика #2464.
bot-backend added 1 commit 2026-08-20 16:25:24 +00:00
fix(ptica): act_date — дата акта об изъятии, а не первой попавшейся ссылки (#2464)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
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 2m0s
CI / backend-tests (pull_request) Successful in 17m13s
9a4acb67d1
`_extract_act_date` брал ПЕРВОЕ «от DD.MM.YYYY» во всём OCR-тексте.
«Сообщение о планируемом изъятии» открывается списком оснований, и первой
строкой там стоит

    «Решение Екатеринбургской городской Думы от 06.07.2004 № 60/1
     «Об утверждении Генерального плана города»»

— Генплан, а не акт об изъятии. На проде это дало 11 строк из 27 с датой
2004-07-06 при проектах 2020 и 2022 годов, причём одну и ту же дату
получили ДВА разных документа (развязка на Сибирском тракте и улица
Энергостроителей). Совпадение даты у несвязанных актов и было первым
признаком, что дата не своя.

Дата принимается, только если в 120 символах перед ней стоит слово
«постановлени». Ссылки-помехи в этих документах — «Решение … Думы» и
«Приказ Министерства» — его не содержат. Окно шире самой фразы, потому
что OCR перемешивает колонки таблицы и вклинивает в неё чужой текст
(«…Администрации города документами) Екатеринбурга от 19.04.2019…»).

Если подходящей даты нет — None. Дата чужого документа хуже пустоты: по
ней нельзя ни отфильтровать актуальные изъятия, ни сверить срок, и она
неотличима от настоящей.

Калибровка не на одном образце: все пять исходных PDF загружены и
распознаны тем же трактом, что использует загрузчик (ocr_pdf_text в
прод-контейнере). Окна 80/120/160 дают одинаковые 5 из 5. Более узкое
правило (плюс «администраци») давало те же 5 из 5, но ломало законный
случай «Постановление № 509-ПП» — областной акт без слова «администрация»,
уже закреплённый тестом test_act_date_extracted_from_text; взято широкое.

Сквозная проверка: патченный код прогнан по всем пяти распознанным
текстам целиком — 27 записей, ровно столько же, сколько строк в
land_reservation; 11 меняют 2004-07-06 на настоящую дату, 16 не двигаются.

Двусторонне: против origin/main три теста красные с реальным неверным
значением ('2004-07-06'), ни одного TypeError — тесты идут через
extract_izyatie_records, чья сигнатура одинакова на обеих сторонах.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bot-backend merged commit 508f2d6090 into main 2026-08-20 16:43:28 +00:00
Author
Collaborator

Важное уточнение: правка действует только вперёд

Проверил, что будет со старыми строками, и ответ — ничего. UPSERT загрузчика заканчивается ON CONFLICT (cad_num, act_number) DO NOTHING, а не DO UPDATE. Пятничный прогон (0 7 * * fri) наткнётся на конфликт и 11 строк с датой Генплана-2004 перезаписаны не будут.

То есть один этот PR оставил бы прод в прежнем состоянии, а выглядел бы как починка. Разовое исправление вынесено отдельно: #2984 (миграция 191, 9 строк → 2022-05-27, 2 строки → 2020-02-12, с сужением по doc_url и контролем-двойником).

### Важное уточнение: правка действует только вперёд Проверил, что будет со старыми строками, и ответ — **ничего**. UPSERT загрузчика заканчивается `ON CONFLICT (cad_num, act_number) DO NOTHING`, а не `DO UPDATE`. Пятничный прогон (`0 7 * * fri`) наткнётся на конфликт и 11 строк с датой Генплана-2004 перезаписаны не будут. То есть один этот PR оставил бы прод в прежнем состоянии, а выглядел бы как починка. Разовое исправление вынесено отдельно: #2984 (миграция 191, 9 строк → 2022-05-27, 2 строки → 2020-02-12, с сужением по doc_url и контролем-двойником).
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#2981
No description provided.