- C-1 (№30): сумма с более чем двумя знаками после запятой не принимается (amount=null, string_value, is_recognized=false); confidence ячеек и шапки приводится к [0, 100], NaN и бесконечность дают 0 - F-A (№28): ошибочный файл набора при успешном понижает статус до needs_check - F-B: «ИНН не распознан» только когда ИНН не извлечён, иначе «ИНН распознан неуверенно» - №29: предупреждения «строк с нечисловым кодом пропущено: N» и «строк без кода с суммами пропущено: N» - I-3 (№31): тексты дат длиннее 200 символов не разбираются и не проходят fix_lookalikes - T1.3 (№33): загрузчик yaml отклоняет повторяющиеся ключи; тавтологичный тест дублей заменён настоящим - гигиена: докстринг amounts.py описывает фактическое поведение (все точки удаляются), мёртвые noqa в ocr/__init__.py Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
64 lines
2.6 KiB
Python
64 lines
2.6 KiB
Python
"""Шапка документа из полей `data_ocr` (§11.1 п. 8): ИНН — только цифры, единицы — очищенный текст, дата отчёта —
|
|
`parse_date` после замены двойников; «распознано» — `confidence == 100` и сырой текст без мусора."""
|
|
|
|
from collections.abc import Mapping
|
|
from dataclasses import dataclass
|
|
from datetime import date
|
|
|
|
from src.normalizer.ocr.dates import is_date_sized, parse_date
|
|
from src.normalizer.ocr.text import clamp_confidence, clean_value, collapse_whitespace, digits_only, fix_lookalikes
|
|
|
|
__all__ = ["FileHeader", "parse_file_header"]
|
|
|
|
FULL_CONFIDENCE = 100.0
|
|
CAPPED_CONFIDENCE = 99.0
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class FileHeader:
|
|
organization_name: str | None
|
|
organization_confidence: float | None
|
|
inn: str | None
|
|
inn_confidence: float | None
|
|
units: str | None
|
|
report_date_text: str | None
|
|
report_date: date | None
|
|
report_date_recognized: bool
|
|
|
|
|
|
def _field(data_ocr: Mapping[str, object], name: str) -> tuple[str, float | None]:
|
|
raw = data_ocr.get(name)
|
|
if not isinstance(raw, Mapping):
|
|
return "", None
|
|
text = raw.get("form_name")
|
|
text_str = str(text) if isinstance(text, str) else ""
|
|
return (text_str, clamp_confidence(raw.get("confidence")))
|
|
|
|
|
|
def parse_file_header(data_ocr: Mapping[str, object], lookalikes: Mapping[str, str]) -> FileHeader:
|
|
organization, organization_confidence = _field(data_ocr, "organization")
|
|
inn_raw, inn_confidence = _field(data_ocr, "inn")
|
|
units_raw, _ = _field(data_ocr, "units")
|
|
date_raw, date_confidence = _field(data_ocr, "date")
|
|
|
|
organization_name = collapse_whitespace(organization) or None
|
|
inn = digits_only(inn_raw) or None
|
|
if inn is None:
|
|
inn_confidence = None
|
|
elif inn_confidence is not None and inn_raw.strip() != inn:
|
|
inn_confidence = min(inn_confidence, CAPPED_CONFIDENCE)
|
|
# решение №31: длинный текст не разбирается и не проходит `fix_lookalikes`
|
|
report_date = (
|
|
parse_date(fix_lookalikes(date_raw, lookalikes)) if date_raw.strip() and is_date_sized(date_raw) else None
|
|
)
|
|
return FileHeader(
|
|
organization_name=organization_name,
|
|
organization_confidence=organization_confidence if organization_name is not None else None,
|
|
inn=inn,
|
|
inn_confidence=inn_confidence,
|
|
units=clean_value(units_raw) or None,
|
|
report_date_text=date_raw.strip() or None,
|
|
report_date=report_date,
|
|
report_date_recognized=date_confidence == FULL_CONFIDENCE and report_date is not None,
|
|
)
|