Andrey Epifancev 412a322b9d chore(arm_statements): копия пакета arm-statements@6bce80b (#52)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:38:43 +04:00

126 lines
4.7 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.

"""Шапка кандидата формы и тип-подсказка по ОКУД и заголовку (§12)."""
import re
from datetime import date
from arm_statements.dates import parse_date
from arm_statements.loader import Rules
from arm_statements.model import Cell, DateField, DocumentType, HeaderCells, HeaderField, StatementHeader
from arm_statements.text import fix_lookalikes, normalize_caption, normalize_label
_INN_LENGTHS = (10, 12)
_NOT_DIGIT = re.compile(r"\D")
def _field(cell: Cell | None) -> HeaderField:
if cell is None:
return HeaderField(None, None, None)
return HeaderField(cell.text, cell.confidence, None)
def _stripped(cell: Cell | None) -> HeaderField:
if cell is None:
return HeaderField(None, None, None)
return HeaderField(cell.text, cell.confidence, cell.text.strip() or None)
def _digits(cell: Cell | None) -> HeaderField:
"""Только цифры текста (пусто — None); двойники не заменяются."""
if cell is None:
return HeaderField(None, None, None)
return HeaderField(cell.text, cell.confidence, _NOT_DIGIT.sub("", cell.text) or None)
def _inn(cell: Cell | None) -> HeaderField:
base = _digits(cell)
if base.value is not None and len(base.value) not in _INN_LENGTHS:
return HeaderField(base.text, base.confidence, None)
return base
def _report_date(cell: Cell | None, rules: Rules) -> DateField:
if cell is None:
return DateField(None, None, None)
value: date | None
if isinstance(cell.value, date):
value = cell.value
else:
value = parse_date(fix_lookalikes(cell.text, rules.lookalikes), rules)
return DateField(cell.text, cell.confidence, value)
def _okud(cell: Cell | None, rules: Rules) -> HeaderField:
if cell is None:
return HeaderField(None, None, None)
match = rules.headers.okud_code.search(normalize_label(cell.text))
return HeaderField(cell.text, cell.confidence, match.group(0) if match else None)
def _units(cell: Cell | None, okei: HeaderField, rules: Rules) -> HeaderField:
base = _field(cell)
if cell is not None:
caption = normalize_caption(cell.text)
for pattern, canonical in rules.units.spellings:
if pattern.search(caption):
return HeaderField(base.text, base.confidence, canonical)
if okei.value is not None:
return HeaderField(base.text, base.confidence, rules.units.okei.get(okei.value))
return base
def parse_header(cells: HeaderCells, rules: Rules) -> StatementHeader:
"""Шапка кандидата: текст и уверенность — из ячейки, значение — по правилам поля."""
okei = _digits(cells.okei)
return StatementHeader(
organization_name=_stripped(cells.organization_name),
inn=_inn(cells.inn),
report_date=_report_date(cells.report_date, rules),
units=_units(cells.units, okei, rules),
okud=_okud(cells.okud, rules),
okpo=_digits(cells.okpo),
okopf=_digits(cells.okopf),
okfs=_digits(cells.okfs),
okei=okei,
address=_stripped(cells.address),
needs_review_fields=(),
)
def _type_by_okud(text: str | None, rules: Rules) -> DocumentType | None:
if not text:
return None
label = normalize_label(text)
for doc_type, patterns in rules.headers.okud.items():
if any(pattern.search(label) for pattern in patterns):
return doc_type
return None
def type_hint(okud_text: str | None, title_text: str | None, rules: Rules) -> DocumentType | None:
"""Тип по ОКУД, иначе по заголовку формы; заголовки колонок не участвуют."""
by_okud = _type_by_okud(okud_text, rules)
if by_okud is not None:
return by_okud
if not title_text:
return None
label = normalize_label(title_text)
for doc_type, patterns in rules.headers.form_titles.items():
if any(pattern.search(label) for pattern in patterns):
return doc_type
return None
def is_continuation(text: str | None, rules: Rules) -> bool:
"""Маркер страницы-продолжения «Форма 0710001 с.2» (ОКУД из словаря не обязателен)."""
if not text:
return False
label = normalize_label(text)
return any(pattern.search(label) for pattern in rules.headers.continuation)
def continuation_type(text: str | None, rules: Rules) -> DocumentType | None:
"""Тип по ОКУД из маркера продолжения; не маркер или неизвестный ОКУД — None."""
if not is_continuation(text, rules):
return None
return _type_by_okud(text, rules)