126 lines
4.7 KiB
Python
126 lines
4.7 KiB
Python
"""Шапка кандидата формы и тип-подсказка по ОКУД и заголовку (§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)
|