AURORA-1734/1735: финальное ревью — сдвинутое значение не отсеивается, XLSB_NO_FORMATS в каждой части (#5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
8f85aa9f19
commit
01968eb121
@ -6,7 +6,7 @@
|
||||
`min_codes` кодов которого — другой формы, начинает новую часть со своими колонками и периодами (`locator` с хвостом
|
||||
`;form:<k>`, `type_hint = None`). Хост вызывает разрез перед `normalize_candidate`; без вызова поведение прежнее.
|
||||
- Редкий столбец с неразобранной подписью между колонками периодов (число не более чем в одной строке с кодом при трёх и
|
||||
более у каждой колонки периода) — не колонка сумм; его числа — в `NUMBERS_OUTSIDE_TABLE` с текстом «числа вне колонок
|
||||
более у каждой колонки периода и числах всех колонок периода в строке его числа) — не колонка сумм; его числа — в `NUMBERS_OUTSIDE_TABLE` с текстом «числа вне колонок
|
||||
формы не учитываются» (AURORA-1735).
|
||||
|
||||
## 0.1.4 — 2026-11-06
|
||||
|
||||
@ -177,12 +177,16 @@ def _is_sparse( # noqa: PLR0913, PLR0917 - внутренняя проверк
|
||||
source: Source,
|
||||
rules: Rules,
|
||||
) -> bool:
|
||||
"""Редкий столбец: колонки периода левее и правее, у него ≤ 1 числа, у каждой колонки периода ≥ 3."""
|
||||
"""Редкий столбец: колонки периода левее и правее, у него ≤ 1 числа, у каждой колонки периода ≥ 3, и в строке его
|
||||
числа у всех колонок периода тоже есть числа — число лишнее, а не сдвинутое OCR из пустой ячейки периода."""
|
||||
if not any(c < col for c in dated) or not any(c > col for c in dated):
|
||||
return False
|
||||
if any(count < _PERIOD_MIN_NUMBERS for count in dated.values()):
|
||||
return False
|
||||
return _number_count(rows, data_rows, col, code_col, source, rules) <= _SPARSE_MAX_NUMBERS
|
||||
hits = [r for r in _code_rows(rows, data_rows, code_col, rules) if _is_number(cell_at(rows, r, col), source, rules)]
|
||||
if len(hits) > _SPARSE_MAX_NUMBERS:
|
||||
return False
|
||||
return all(_is_number(cell_at(rows, r, period), source, rules) for r in hits for period in dated)
|
||||
|
||||
|
||||
def _last_dated_column( # noqa: PLR0913, PLR0917 - внутренняя сборка границы таблицы
|
||||
|
||||
@ -8,10 +8,14 @@
|
||||
from dataclasses import replace
|
||||
|
||||
from arm_statements.codes import document_type_of, majority_section, section_of
|
||||
from arm_statements.issues import WarningCode
|
||||
from arm_statements.loader import Rules, default_rules
|
||||
from arm_statements.model import DocumentType, Evidence, FormCandidate
|
||||
from arm_statements.segments import split_segments
|
||||
|
||||
# предупреждения источника, нужные каждой части: без числовых форматов xlsb шаг 4 иначе проверяет масштаб (ревью I-2)
|
||||
_SOURCE_WARNINGS = frozenset({WarningCode.XLSB_NO_FORMATS})
|
||||
|
||||
|
||||
def _opening_type(codes: tuple[str, ...], rules: Rules) -> DocumentType | None:
|
||||
"""Тип формы, которой сегмент открывается: первые `min_codes` кодов одной формы; иначе `None`."""
|
||||
@ -42,7 +46,8 @@ def split_by_form(candidate: FormCandidate, rules: Rules | None = None) -> tuple
|
||||
"""Части кандидата по смене формы; без смены — `(candidate,)` тем же объектом.
|
||||
|
||||
Первая часть сохраняет поля исходного кандидата (кроме строк и границ листов); часть `k` (с 1) — `locator`
|
||||
с хвостом `;form:<k>`, `type_hint = None`, пустые `evidence` и `warnings`: тип — только по кодам на шаге 6.
|
||||
с хвостом `;form:<k>`, `type_hint = None`, пустые `evidence`, из `warnings` — только предупреждения источника
|
||||
(`XLSB_NO_FORMATS`): тип — только по кодам на шаге 6.
|
||||
"""
|
||||
rules = rules if rules is not None else default_rules()
|
||||
cuts = _cuts(candidate, rules)
|
||||
@ -64,7 +69,7 @@ def split_by_form(candidate: FormCandidate, rules: Rules | None = None) -> tuple
|
||||
evidence=Evidence(okud=None, title=None),
|
||||
rows=rows,
|
||||
segment_breaks=breaks,
|
||||
warnings=(),
|
||||
warnings=tuple(w for w in candidate.warnings if w.code in _SOURCE_WARNINGS),
|
||||
)
|
||||
)
|
||||
return tuple(parts)
|
||||
|
||||
@ -257,3 +257,15 @@ def test_sparse_column_without_period_to_the_right_stays(rules: Rules) -> None:
|
||||
assert doc is not None
|
||||
assert [c.column_index for c in doc.columns] == [2, 3, 4]
|
||||
assert SPARSE_COL in [w.column_index for w in doc.warnings if w.code == "COLUMN_WITHOUT_DATE"]
|
||||
|
||||
|
||||
def test_shifted_value_in_sparse_column_keeps_column(rules: Rules) -> None:
|
||||
"""Единственное число столбца стоит в строке, где колонка периода пуста, — это сдвиг OCR, не мусор (ревью I-1)."""
|
||||
rows = kaltan_rows()
|
||||
rows[5][3] = None # без постороннего «$301»: единственное число столбца — сдвинутое значение 2460
|
||||
rows.insert(5, ["Прочее", "2460", "12", "55", None])
|
||||
doc = normalize_candidate(ocr(rows), rules).document
|
||||
assert doc is not None
|
||||
assert [c.column_index for c in doc.columns] == [2, 3, 4]
|
||||
assert SPARSE_COL in [w.column_index for w in doc.warnings if w.code == "COLUMN_WITHOUT_DATE"]
|
||||
assert doc.status is DocStatus.NEEDS_CHECK
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
from datetime import date
|
||||
|
||||
from arm_statements import normalize_candidate, split_by_form
|
||||
from arm_statements import WarningCode, make_warning, normalize_candidate, split_by_form
|
||||
from arm_statements.loader import Rules
|
||||
from arm_statements.model import DocumentType, Evidence
|
||||
from tests.builders import excel, hdr, ocr
|
||||
@ -102,3 +102,12 @@ def test_excel_breaks_shifted(rules: Rules) -> None:
|
||||
assert inc.segment_breaks == (0, len(income_rows()))
|
||||
assert inc.locator == f"{cand.locator};form:1"
|
||||
assert len(inc.rows) == len(income_rows()) + len(continuation)
|
||||
|
||||
|
||||
def test_xlsb_source_warning_kept_in_every_part(rules: Rules) -> None:
|
||||
"""`XLSB_NO_FORMATS` — свойство источника, нужно каждой части для проверки масштаба (ревью I-2)."""
|
||||
no_formats = make_warning(WarningCode.XLSB_NO_FORMATS, "xlsb без числовых форматов", rules)
|
||||
cand = excel([("Б", balance_rows()), ("О", income_rows())], header=HEADER, warnings=[no_formats])
|
||||
bal, inc = split_by_form(cand, rules)
|
||||
assert bal.warnings == (no_formats,)
|
||||
assert inc.warnings == (no_formats,)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user