Портирован модуль date_utils.py из DKB_ARMKA с перечисленными в §11.1 правками: - удалены _LOOKALIKES, _LOOKALIKE_CHARS, _NEAR_DIGIT, _normalize (перенесены в text.fix_lookalikes) - переименована _parse_date в parse_date с новой сигнатурой (value: object -> date | None) - обновлены аннотации типов: Optional[...] -> ... | None - добавлена аннотация для _PATTERNS: list[tuple[re.Pattern[str], Callable[[re.Match[str]], date | None]]] - добавлены noqa/type: ignore комментарии для прохода линтеров Тесты: 35 тестов пройдены (парсинг дат в текстовых и числовых формах, отклонение названий организаций). Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
71 lines
2.8 KiB
Python
71 lines
2.8 KiB
Python
"""Разбор дат из OCR-текста (§11.1 п. 2): дословный перенос `date_utils` старого ядра, вход — после `fix_lookalikes`."""
|
||
|
||
from datetime import date
|
||
|
||
import pytest
|
||
|
||
from src.normalizer.ocr.dates import parse_date
|
||
from src.normalizer.ocr.text import fix_lookalikes
|
||
|
||
LOOKALIKES = {"О": "0", "о": "0", "З": "3", "l": "1", "I": "1", "|": "1", "б": "6", "S": "5", "Ч": "4"}
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
("raw", "expected"),
|
||
[
|
||
("на 30 сентября 2025 г.", date(2025, 9, 30)),
|
||
("На 30 июня\n2025 г", date(2025, 6, 30)),
|
||
("31 декабря 2024 года", date(2024, 12, 31)),
|
||
("30 сентября 2025 r.", date(2025, 9, 30)), # латинская r вместо г
|
||
("с 01.01.2025 по 30.09.2025", date(2025, 9, 30)), # диапазон — конец
|
||
("I квартал 2026", date(2026, 3, 31)),
|
||
("1-й кв. 2026", date(2026, 3, 31)),
|
||
("первый квартал 2025 г.", date(2025, 3, 31)),
|
||
("1 полугодие 2026", date(2026, 6, 30)),
|
||
("1 п/г 2026", date(2026, 6, 30)),
|
||
("9 месяцев 2025", date(2025, 9, 30)),
|
||
("9 мес. 2025", date(2025, 9, 30)),
|
||
("За январь - июнь\n2025 г.", date(2025, 6, 30)),
|
||
("январь – сентябрь 2025", date(2025, 9, 30)),
|
||
("за 2025 г.", date(2025, 12, 31)),
|
||
("2024 год", date(2024, 12, 31)),
|
||
("30.09.2025", date(2025, 9, 30)),
|
||
("2025 03 31", date(2025, 3, 31)),
|
||
("30/09/25", date(2025, 9, 30)),
|
||
("2 0 2 4", date(2024, 12, 31)),
|
||
("30 09 2 0 2 5", date(2025, 9, 30)),
|
||
("30092025", date(2025, 9, 30)),
|
||
("20250930", date(2025, 9, 30)),
|
||
("30 09 25", date(2025, 9, 30)),
|
||
("30.09.2025 0:00:00", date(2025, 9, 30)),
|
||
("2|0|2|4|", date(2024, 12, 31)), # через fix_lookalikes
|
||
("3l.12.2О24", date(2024, 12, 31)),
|
||
],
|
||
)
|
||
def test_parses_real_forms(raw: str, expected: date) -> None:
|
||
assert parse_date(fix_lookalikes(raw, LOOKALIKES)) == expected
|
||
|
||
|
||
@pytest.mark.parametrize(
|
||
"raw",
|
||
[
|
||
"Общество с ограниченной ответственностью", # Review Focus 3: название организации — не дата
|
||
"ЗАКРЫТОЕ АКЦИОНЕРНОЕ ОБЩЕСТВО",
|
||
"",
|
||
"+",
|
||
"тыс. руб.",
|
||
"1999999999999", # 13 цифр — ни одна форма
|
||
],
|
||
)
|
||
def test_organization_name_is_not_a_date(raw: str) -> None:
|
||
assert parse_date(fix_lookalikes(raw, LOOKALIKES)) is None
|
||
|
||
|
||
def test_non_string_is_none() -> None:
|
||
assert parse_date(None) is None
|
||
assert parse_date(20250930) is None
|
||
|
||
|
||
def test_year_out_of_range_is_none() -> None:
|
||
assert parse_date("1 2 3 4") is None # 1234 вне 1990–2100
|