fix(ptica): тип постановления — по первому упоминанию, а не по порядку проверок (#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 1m56s
CI / backend-tests (pull_request) Successful in 17m12s
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 1m56s
CI / backend-tests (pull_request) Successful in 17m12s
`_detect_kind` проверял «резервир» первым и возвращал «резервирование» безусловно. Постановление об изъятии, где резервирование упомянуто вскользь — типовая формулировка «ранее зарезервированных земель», ссылка на утративший силу акт, — классифицировалось как резервирование. Ошибка не единичная: `kind` в `extract_reservations` один на весь документ, поэтому неверный тип уходит в КАЖДУЮ строку land_reservation по этому акту. Побеждает то слово, что встретилось раньше. Тема документа стоит в заголовке, поэтому позиция — сигнал сильнее порядка проверок, и он симметричен: заголовок «О резервировании» так же выигрывает у «изъятия» в теле. Простая смена порядка проверок этой симметрии не даёт — на неё поставлен отдельный контроль. Текущих ошибок на проде нет, и это измерено: в land_reservation 27 строк, все из источника izyatie_ekb_ocr, все «изъятие», ни в одной выдержке слова «резервир» не встречается. Правка закрывает возможность, а не чинит существующую порчу. Двусторонне: против origin/main два теста красные с конкретным неверным значением (`assert 'резервирование' == 'изъятие'`). Контроли — симметрия заголовка, одиночные маркеры, откат к default_kind — зелёные с обеих сторон. Формулировки в тестах взяты с прода дословно (basis_act). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
610ed20395
commit
71f27fee7d
2 changed files with 100 additions and 3 deletions
|
|
@ -127,10 +127,24 @@ def _parse_act_date(text: str) -> date | None:
|
|||
|
||||
|
||||
def _detect_kind(text: str, default_kind: str) -> str:
|
||||
"""Определяет тип операции: 'резервирование' | 'изъятие' | default_kind."""
|
||||
if _RE_REZERV.search(text):
|
||||
"""Определяет тип операции: 'резервирование' | 'изъятие' | default_kind.
|
||||
|
||||
Побеждает то слово, что встретилось РАНЬШЕ, а не то, что стоит выше в
|
||||
коде (#2464). Прежний безусловный приоритет «резервир» переклассифицировал
|
||||
ВЕСЬ документ — все участки разом, — если постановление об изъятии хоть
|
||||
раз ссылалось на резервирование (типовая формулировка «ранее
|
||||
зарезервированных земель», ссылка на утративший силу акт). Тема документа
|
||||
стоит в заголовке, поэтому позиция первого упоминания — сигнал сильнее
|
||||
порядка проверок, и он симметричен: заголовок «О резервировании» так же
|
||||
выигрывает у «изъятия» в теле.
|
||||
"""
|
||||
m_rez = _RE_REZERV.search(text)
|
||||
m_izy = _RE_IZYAT.search(text)
|
||||
if m_rez and m_izy:
|
||||
return "резервирование" if m_rez.start() < m_izy.start() else "изъятие"
|
||||
if m_rez:
|
||||
return "резервирование"
|
||||
if _RE_IZYAT.search(text):
|
||||
if m_izy:
|
||||
return "изъятие"
|
||||
return default_kind
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
"""Тип постановления определяется по первому упоминанию, а не по порядку проверок (#2464).
|
||||
|
||||
`_detect_kind` проверял «резервир» ПЕРВЫМ и возвращал «резервирование» безусловно.
|
||||
Постановление об изъятии, где резервирование упомянуто вскользь — типовая
|
||||
формулировка «ранее зарезервированных земель», ссылка на утративший силу акт, —
|
||||
переклассифицировало **весь документ**: `kind` в `extract_reservations` один на
|
||||
все участки, поэтому ошибка размножается на каждую строку `land_reservation`.
|
||||
|
||||
Побеждает то слово, что встретилось раньше. Тема документа стоит в заголовке,
|
||||
поэтому позиция — сигнал сильнее порядка проверок, и он симметричен: заголовок
|
||||
«О резервировании» так же выигрывает у «изъятия» в теле.
|
||||
|
||||
Формулировки взяты с прода: `basis_act` в `land_reservation` (источник
|
||||
`izyatie_ekb_ocr`, 27 строк, все «изъятие») начинается со слов «Сообщение о
|
||||
планируемом изъятии земельных участков…».
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from app.services.scrapers.page_reservation_parser import (
|
||||
_detect_kind,
|
||||
extract_reservations,
|
||||
)
|
||||
|
||||
# Заголовок — с прода, дословно.
|
||||
_ЗАГОЛОВОК_ИЗЪЯТИЕ = (
|
||||
"Сообщение о планируемом изъятии земельных участков и объектов недвижимого "
|
||||
"имущества для муниципальных нужд в целях строительства улицы Энергостроителей"
|
||||
)
|
||||
|
||||
|
||||
def test_izyatie_in_title_wins_over_later_rezervirovanie() -> None:
|
||||
"""Головной: упоминание резервирования в теле не переписывает тему заголовка.
|
||||
|
||||
На origin/main возвращается «резервирование» — неверное значение, а не отказ.
|
||||
"""
|
||||
text = (
|
||||
f"{_ЗАГОЛОВОК_ИЗЪЯТИЕ}. Изъятию подлежат участки, ранее зарезервированные "
|
||||
"постановлением о резервировании земель от 12.03.2019."
|
||||
)
|
||||
assert (
|
||||
_detect_kind(text, "изъятие") == "изъятие"
|
||||
), "документ об изъятии классифицирован по случайному упоминанию в теле"
|
||||
|
||||
|
||||
def test_all_parcels_of_the_document_are_affected() -> None:
|
||||
"""Ошибка типа размножается на КАЖДЫЙ участок — kind в документе один на всех."""
|
||||
text = (
|
||||
f"{_ЗАГОЛОВОК_ИЗЪЯТИЕ}. Изъять земельные участки с кадастровыми номерами "
|
||||
"66:41:0303004:22, 66:41:0303004:23, 66:41:0206032:8499, ранее "
|
||||
"зарезервированные постановлением о резервировании земель."
|
||||
)
|
||||
records = extract_reservations(text, default_kind="изъятие")
|
||||
assert len(records) == 3, f"разобрано участков: {len(records)}"
|
||||
kinds = {r.reservation_kind for r in records}
|
||||
assert kinds == {"изъятие"}, f"типы участков: {kinds} — ошибка ушла бы во все строки"
|
||||
|
||||
|
||||
def test_rezervirovanie_in_title_still_wins() -> None:
|
||||
"""Контроль симметрии: заголовок о резервировании выигрывает у изъятия в теле.
|
||||
|
||||
Ловит «починку» через простую смену порядка проверок — она сломала бы этот случай.
|
||||
"""
|
||||
text = (
|
||||
"Постановление о резервировании земель для муниципальных нужд. "
|
||||
"Зарезервированные участки не подлежат изъятию до истечения срока."
|
||||
)
|
||||
assert _detect_kind(text, "изъятие") == "резервирование"
|
||||
|
||||
|
||||
def test_single_keyword_documents_unchanged() -> None:
|
||||
"""Контроль: документы с одним словом-маркером распознаются как раньше."""
|
||||
assert _detect_kind("Об изъятии земельного участка", "резервирование") == "изъятие"
|
||||
assert _detect_kind("О резервировании земель", "изъятие") == "резервирование"
|
||||
|
||||
|
||||
def test_no_keyword_falls_back_to_default() -> None:
|
||||
"""Контроль: без маркеров остаётся default_kind, а не выдуманный тип."""
|
||||
assert _detect_kind("Об утверждении схемы расположения участка", "изъятие") == "изъятие"
|
||||
assert (
|
||||
_detect_kind("Об утверждении схемы расположения участка", "резервирование")
|
||||
== "резервирование"
|
||||
)
|
||||
Loading…
Add table
Reference in a new issue