gendesign/backend/tests/services/scrapers/test_2464_act_date_citation.py
bot-backend 9a4acb67d1
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
fix(ptica): act_date — дата акта об изъятии, а не первой попавшейся ссылки (#2464)
`_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>
2026-08-20 21:24:32 +05:00

149 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""act_date — дата акта об изъятии, а не первой попавшейся ссылки (#2464).
`_extract_act_date` брал ПЕРВОЕ «от DD.MM.YYYY» во всём OCR-тексте. «Сообщение о
планируемом изъятии» открывается списком оснований, и первой строкой там почти
всегда стоит
«Решение Екатеринбургской городской Думы от 06.07.2004 № 60/1
«Об утверждении Генерального плана города»»
— то есть Генплан, а не акт об изъятии. На проде это дало 11 строк из 27
(два РАЗНЫХ документа: развязка на Сибирском тракте и улица Энергостроителей)
с одной датой 2004-07-06 при проектах 2020 и 2022 годов.
Проверено не по догадке: все пять исходных PDF были загружены и распознаны тем
же трактом, что использует загрузчик (`ocr_pdf_text` в прод-контейнере). Тексты
цитат ниже — дословно оттуда, включая перемешивание колонок OCR'ом.
Правило («постановлени» в 120 символах перед датой) откалибровано на всех пяти
документах сразу, а не на одном: окна 80/120/160 дают одинаковые 5 из 5.
Более узкий вариант (плюс «администраци») давал те же 5 из 5, но ломал законный
случай «Постановление № 509-ПП» — областной акт без слова «администрация»,
уже закреплённый тестом test_act_date_extracted_from_text. Взято широкое условие.
Патченный код прогнан по всем пяти распознанным текстам целиком: 27 записей —
ровно столько же, сколько строк в land_reservation, из них 11 меняют дату
2004-07-06 на настоящую, 16 остаются прежними.
"""
from __future__ import annotations
from app.services.scrapers.izyatie_ocr import _extract_act_date, extract_izyatie_records
# Заголовок раздела — дословно с прода (basis_act в land_reservation).
ЗАГОЛОВОК = (
"Сообщение о планируемом изъятии земельных участков и объектов недвижимого "
"имущества для муниципальных нужд"
)
КАД = "66:41:0303004:22"
def атааписи(текст_документа: str) -> str | None:
"""act_date так, как её увидит land_reservation — через сквозной путь.
Зовём `extract_izyatie_records`, а не приватный хелпер: его сигнатура
одинакова на обеих сторонах, поэтому против origin/main тест краснеет
НЕВЕРНЫМ ЗНАЧЕНИЕМ (дата Генплана), а не TypeError из-за нового параметра.
"""
записи = extract_izyatie_records(f"{текст_документа}\n{КАД}", ЗАГОЛОВОК, "http://x/y")
assert записи, "кад-номер не разобран — тест смотрел бы не туда"
return записи[0]["act_date"]
# ── Дословные фрагменты OCR прод-документов ───────────────────────────────────
# 89adb28a… — развязка Базовый/Комсомольская/Сибирский тракт. На проде дал 2004-07-06.
ДОК_РАЗВЯЗКА = (
"Администрация города Екатеринбурга\n\n"
"— Решение Екатеринбургской городской Думы от\n06.07.2004 № 60/1 «Об утверждении\n"
"Генерального плана развития городского округа»;\n\n"
"— Приказ Министерства строительства и развития\nинфраструктуры Свердловской области от\n"
"30.12.2021 № 746-П;\n\n"
"— Постановление Администрации города\nЕкатеринбурга от 27.05.2022 № 1413\n"
"«Об утверждении проекта планировки и проекта межевания территории»"
)
# 9b9d9a99… — улица Энергостроителей. На проде тоже дал 2004-07-06.
ДОКНЕРГОСТРОИТЕЛЕЙ = (
"Администрация города Екатеринбурга\n\n"
"— Решение Екатеринбургской городской Думы от\n06.07.2004 № 60/1 «Об утверждении\n"
"Генерального плана развития городского округа»;\n\n"
"— Приказ Министерства строительства и развития\nинфраструктуры Свердловской области от\n"
"30.12.2021 № 746-П «О внесении изменений в Генеральный план на период до 2025 года»;\n\n"
"— Постановление Администрации города\nЕкатеринбурга от 12.02.2020 № 259 «Об утверждении\n"
"проекта планировки и проекта межевания территории»"
)
# bf2bb1bd… — OCR перемешал колонки таблицы: слова фразы идут вперемешку.
ДОКЕРЕМЕШАННЫЙ = (
"Постановление Администрации города утверждении проекта планировки\n"
"Екатеринбурга от 29.09.2015 № 2687 территории и проекта межевания\n"
"«Об утверждении» (в ред. Постановления Администрации города\n"
"Екатеринбурга от 14.07.2022 № 1995)"
)
# ccd00215… — то же перемешивание, но короче.
ДОКЕРЕМЕШАННЫЙ_2 = (
"Реквизиты документов (при наличии таких Постановление Администрации города\n"
"документами) Екатеринбурга от 19.04.2019 № 863 «Об\nутверждении проекта межевания территории»"
)
# 188a5c36… — первой же датой идёт нужное постановление.
ДОКРЯМОЙ = (
"Реквизиты документов (при наличии таких документами)\n"
"Постановление Администрации города Екатеринбурга от 24.06.2019 № 1504\n"
"«Об утверждении проекта планировки и проекта межевания территории»"
)
def test_genplan_citation_is_not_taken_as_act_date() -> None:
"""Головной: дата Генплана-2004 не должна становиться датой изъятия.
На origin/main возвращается '2004-07-06' — дата чужого документа,
на 18 лет раньше настоящего основания.
"""
got = атааписи(ДОК_РАЗВЯЗКА)
assert got != "2004-07-06", "взята дата Решения Думы об утверждении Генерального плана"
assert got == "2022-05-27", f"ожидали дату постановления № 1413, получили {got!r}"
def test_second_document_with_same_genplan_citation() -> None:
"""Тот же дефект во втором документе — он и дал одинаковую дату у 11 строк."""
got = атааписи(ДОКНЕРГОСТРОИТЕЛЕЙ)
assert got == "2020-02-12", f"ожидали дату постановления № 259, получили {got!r}"
def test_ministry_order_is_not_taken_either() -> None:
"""Контроль: приказ Министерства между Генпланом и постановлением тоже не берётся."""
got = атааписи(ДОК_РАЗВЯЗКА)
assert got != "2021-12-30", "взята дата приказа Министерства строительства"
def test_ocr_column_scramble_still_resolves() -> None:
"""Контроль ширины окна: перемешанные OCR'ом колонки не должны ломать разбор.
Окно шире самой фразы (~50 символов) именно ради этих двух документов:
между «Постановление» и «от DD.MM.YYYY» вклинивается текст соседней колонки.
"""
assert атааписи(ДОКЕРЕМЕШАННЫЙ) == "2015-09-29"
assert атааписи(ДОКЕРЕМЕШАННЫЙ_2) == "2019-04-19"
def test_correct_documents_unchanged() -> None:
"""Контроль от переусердствования: там, где на проде было верно, ничего не меняется."""
assert атааписи(ДОКРЯМОЙ) == "2019-06-24"
def test_no_act_citation_gives_none_not_a_wrong_date() -> None:
"""Контроль: без основания — None, а не дата постороннего документа.
Отсутствие даты честнее: по чужой дате нельзя ни отфильтровать актуальные
изъятия, ни сверить срок, и она неотличима от настоящей.
"""
текст = "Решение Екатеринбургской городской Думы от 06.07.2004 № 60/1 «Об утверждении»"
assert атааписи(текст) is None
def test_title_path_does_not_require_context() -> None:
"""Контроль: у заголовка ссылок на чужие акты нет, там правило не применяется."""
assert _extract_act_date("Постановление от 27.05.2022 № 1413") == "2022-05-27"