71 lines
3.8 KiB
Python
71 lines
3.8 KiB
Python
"""Разрез кандидата по смене формы до шагов 2–8 (спека «Шаг 2 — разрез кандидата по смене формы», AURORA-1734).
|
||
|
||
ИС Сканирование может вернуть ББ и ОФР одной таблицей: шапка ОФР внутри таблицы — повторная шапка, и без разреза строки
|
||
ОФР попадают в ББ под его колонками. Часть начинается с сегмента со своей шапкой, первые `min_codes` кодов которого —
|
||
одной формы, другой, чем у текущей части (тип части — по большинству кодов её первого сегмента с кодами).
|
||
"""
|
||
|
||
from dataclasses import replace
|
||
|
||
from arm_statements.codes import document_type_of, majority_section, section_of
|
||
from arm_statements.loader import Rules, default_rules
|
||
from arm_statements.model import DocumentType, Evidence, FormCandidate
|
||
from arm_statements.segments import split_segments
|
||
|
||
|
||
def _opening_type(codes: tuple[str, ...], rules: Rules) -> DocumentType | None:
|
||
"""Тип формы, которой сегмент открывается: первые `min_codes` кодов одной формы; иначе `None`."""
|
||
head = codes[: rules.code_ranges.min_codes]
|
||
if len(head) < rules.code_ranges.min_codes:
|
||
return None
|
||
kinds = {None if (s := section_of(code, rules)) is None else document_type_of(s) for code in head}
|
||
return kinds.pop() if len(kinds) == 1 else None
|
||
|
||
|
||
def _cuts(candidate: FormCandidate, rules: Rules) -> list[int]:
|
||
"""Строки сетки, с которых начинаются части после первой."""
|
||
cuts: list[int] = []
|
||
current: DocumentType | None = None
|
||
for segment in split_segments(candidate, rules):
|
||
if current is None:
|
||
section = majority_section(list(segment.codes), rules)
|
||
current = None if section is None else document_type_of(section)
|
||
continue
|
||
opening = _opening_type(segment.codes, rules)
|
||
if segment.has_header and opening is not None and opening is not current and segment.first > 0:
|
||
cuts.append(segment.first)
|
||
current = opening
|
||
return cuts
|
||
|
||
|
||
def split_by_form(candidate: FormCandidate, rules: Rules | None = None) -> tuple[FormCandidate, ...]:
|
||
"""Части кандидата по смене формы; без смены — `(candidate,)` тем же объектом.
|
||
|
||
Первая часть сохраняет поля исходного кандидата (кроме строк и границ листов); часть `k` (с 1) — `locator`
|
||
с хвостом `;form:<k>`, `type_hint = None`, пустые `evidence` и `warnings`: тип — только по кодам на шаге 6.
|
||
"""
|
||
rules = rules if rules is not None else default_rules()
|
||
cuts = _cuts(candidate, rules)
|
||
if not cuts:
|
||
return (candidate,)
|
||
bounds = [0, *cuts, len(candidate.rows)]
|
||
parts: list[FormCandidate] = []
|
||
for k, (start, end) in enumerate(zip(bounds, bounds[1:], strict=False)):
|
||
breaks = tuple(b - start for b in candidate.segment_breaks if start <= b < end)
|
||
rows = candidate.rows[start:end]
|
||
if k == 0:
|
||
parts.append(replace(candidate, rows=rows, segment_breaks=breaks))
|
||
else:
|
||
parts.append(
|
||
replace(
|
||
candidate,
|
||
locator=f"{candidate.locator};form:{k}",
|
||
type_hint=None,
|
||
evidence=Evidence(okud=None, title=None),
|
||
rows=rows,
|
||
segment_breaks=breaks,
|
||
warnings=(),
|
||
)
|
||
)
|
||
return tuple(parts)
|