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` с хвостом
|
`min_codes` кодов которого — другой формы, начинает новую часть со своими колонками и периодами (`locator` с хвостом
|
||||||
`;form:<k>`, `type_hint = None`). Хост вызывает разрез перед `normalize_candidate`; без вызова поведение прежнее.
|
`;form:<k>`, `type_hint = None`). Хост вызывает разрез перед `normalize_candidate`; без вызова поведение прежнее.
|
||||||
- Редкий столбец с неразобранной подписью между колонками периодов (число не более чем в одной строке с кодом при трёх и
|
- Редкий столбец с неразобранной подписью между колонками периодов (число не более чем в одной строке с кодом при трёх и
|
||||||
более у каждой колонки периода) — не колонка сумм; его числа — в `NUMBERS_OUTSIDE_TABLE` с текстом «числа вне колонок
|
более у каждой колонки периода и числах всех колонок периода в строке его числа) — не колонка сумм; его числа — в `NUMBERS_OUTSIDE_TABLE` с текстом «числа вне колонок
|
||||||
формы не учитываются» (AURORA-1735).
|
формы не учитываются» (AURORA-1735).
|
||||||
|
|
||||||
## 0.1.4 — 2026-11-06
|
## 0.1.4 — 2026-11-06
|
||||||
|
|||||||
@ -177,12 +177,16 @@ def _is_sparse( # noqa: PLR0913, PLR0917 - внутренняя проверк
|
|||||||
source: Source,
|
source: Source,
|
||||||
rules: Rules,
|
rules: Rules,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Редкий столбец: колонки периода левее и правее, у него ≤ 1 числа, у каждой колонки периода ≥ 3."""
|
"""Редкий столбец: колонки периода левее и правее, у него ≤ 1 числа, у каждой колонки периода ≥ 3, и в строке его
|
||||||
|
числа у всех колонок периода тоже есть числа — число лишнее, а не сдвинутое OCR из пустой ячейки периода."""
|
||||||
if not any(c < col for c in dated) or not any(c > col for c in dated):
|
if not any(c < col for c in dated) or not any(c > col for c in dated):
|
||||||
return False
|
return False
|
||||||
if any(count < _PERIOD_MIN_NUMBERS for count in dated.values()):
|
if any(count < _PERIOD_MIN_NUMBERS for count in dated.values()):
|
||||||
return False
|
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 - внутренняя сборка границы таблицы
|
def _last_dated_column( # noqa: PLR0913, PLR0917 - внутренняя сборка границы таблицы
|
||||||
|
|||||||
@ -8,10 +8,14 @@
|
|||||||
from dataclasses import replace
|
from dataclasses import replace
|
||||||
|
|
||||||
from arm_statements.codes import document_type_of, majority_section, section_of
|
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.loader import Rules, default_rules
|
||||||
from arm_statements.model import DocumentType, Evidence, FormCandidate
|
from arm_statements.model import DocumentType, Evidence, FormCandidate
|
||||||
from arm_statements.segments import split_segments
|
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:
|
def _opening_type(codes: tuple[str, ...], rules: Rules) -> DocumentType | None:
|
||||||
"""Тип формы, которой сегмент открывается: первые `min_codes` кодов одной формы; иначе `None`."""
|
"""Тип формы, которой сегмент открывается: первые `min_codes` кодов одной формы; иначе `None`."""
|
||||||
@ -42,7 +46,8 @@ def split_by_form(candidate: FormCandidate, rules: Rules | None = None) -> tuple
|
|||||||
"""Части кандидата по смене формы; без смены — `(candidate,)` тем же объектом.
|
"""Части кандидата по смене формы; без смены — `(candidate,)` тем же объектом.
|
||||||
|
|
||||||
Первая часть сохраняет поля исходного кандидата (кроме строк и границ листов); часть `k` (с 1) — `locator`
|
Первая часть сохраняет поля исходного кандидата (кроме строк и границ листов); часть `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()
|
rules = rules if rules is not None else default_rules()
|
||||||
cuts = _cuts(candidate, 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),
|
evidence=Evidence(okud=None, title=None),
|
||||||
rows=rows,
|
rows=rows,
|
||||||
segment_breaks=breaks,
|
segment_breaks=breaks,
|
||||||
warnings=(),
|
warnings=tuple(w for w in candidate.warnings if w.code in _SOURCE_WARNINGS),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
return tuple(parts)
|
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 doc is not None
|
||||||
assert [c.column_index for c in doc.columns] == [2, 3, 4]
|
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 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 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.loader import Rules
|
||||||
from arm_statements.model import DocumentType, Evidence
|
from arm_statements.model import DocumentType, Evidence
|
||||||
from tests.builders import excel, hdr, ocr
|
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.segment_breaks == (0, len(income_rows()))
|
||||||
assert inc.locator == f"{cand.locator};form:1"
|
assert inc.locator == f"{cand.locator};form:1"
|
||||||
assert len(inc.rows) == len(income_rows()) + len(continuation)
|
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