fix(statements): верхняя строка двухстрочного заголовка без подписи «Код» не теряется (T13.I2)
Без подписи «Код» строкой заголовка выбиралась нижняя строка с датами; верхняя («На 31 декабря», «Изменение, %») оставалась над заголовком: колонка без периода не попадала в skipped_labels, метки периодов не склеивались. Тест синтетики st02 проверяет точное предупреждение, две колонки и отсутствие значений «Изменение, %». Refs #14 Co-Authored-By: Claude Sonnet <noreply@anthropic.com> Claude-Session: cedd7915-2efe-41e5-988c-a9117033598b
This commit is contained in:
parent
fad0c10412
commit
e7552dcdb0
@ -134,10 +134,22 @@ def _find_header_row(sheet: Sheet, rules: Rules, code_col: int, first_row: int,
|
||||
strong = [label for label in labels if _is_period_label(label, rules, allow_bare_year=False)]
|
||||
weak = [label for label in labels if _is_period_label(label, rules, allow_bare_year=True)]
|
||||
if strong or len(weak) >= 2: # noqa: PLR2004 - две даты подряд — заголовок, одна «2025» — титул
|
||||
return row
|
||||
return _upper_header_row(sheet, rules, row, code_col, first_row)
|
||||
return None
|
||||
|
||||
|
||||
def _upper_header_row(sheet: Sheet, rules: Rules, row: int, code_col: int, first_row: int) -> int:
|
||||
"""Без подписи «Код» найденная строка — нижняя в двухстрочном заголовке: строка выше с подписями только правее кодов
|
||||
и без чисел («На 31 декабря» / «Изменение, %») — его первая строка."""
|
||||
above = row - 1
|
||||
if above < first_row or is_numbering_row(sheet, above, rules) or sheet.text_at(above, code_col) != "":
|
||||
return row
|
||||
cells = sheet.row_cells(above)
|
||||
if any(cell.kind == "number" for _, cell in cells) or not any(col > code_col for col, _ in cells):
|
||||
return row
|
||||
return above
|
||||
|
||||
|
||||
def _header_last_row(sheet: Sheet, rules: Rules, header_row: int, code_col: int, first_code_row: int) -> int:
|
||||
"""Вторая строка заголовка (продолжение подписей без кодов) и строка нумерации колонок."""
|
||||
last = header_row
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
файл — вход, оракулом он не служит. Без каталога fixtures тесты пропускаются (маркер `fixtures`).
|
||||
"""
|
||||
|
||||
from datetime import date
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
@ -14,6 +15,7 @@ from result_contract import assert_valid_result
|
||||
from src.contract import FileType, OsvDocument, ResultStatus, StatementDocument
|
||||
from src.contract.result import StatementLine
|
||||
from src.parser import ParseOutcome, get_parser
|
||||
from src.parser.statements.statement import SKIPPED_COLUMN_WARNING
|
||||
from tests.fixtures_helpers import statement_synthetic_skip_reason
|
||||
from tests.synthetic.generate_statements import FILE_NAMES
|
||||
|
||||
@ -145,6 +147,12 @@ def test_st02_csv_like_format(fixtures_dir: Path) -> None:
|
||||
# Проверяем колонки: две даты
|
||||
assert len(doc.columns) == EXPECTED_COLUMN_COUNT
|
||||
assert all(col.period_date is not None for col in doc.columns)
|
||||
assert [col.period_date for col in doc.columns] == [date(2024, 12, 31), date(2023, 12, 31)]
|
||||
# T13.I2: «Изменение, %» не колонка, её значения (5) не читаются, предупреждение — ровно одно
|
||||
assert all("Изменение" not in col.label for col in doc.columns)
|
||||
assert all(len(line.values) == EXPECTED_COLUMN_COUNT for line in doc.lines)
|
||||
skipped = [w for w in warnings_of(outcome) if SKIPPED_COLUMN_WARNING in w]
|
||||
assert skipped == [f"sheet=Лист1 {SKIPPED_COLUMN_WARNING}: Изменение, %"]
|
||||
|
||||
# Проверяем код 1210 = -50
|
||||
lines_by_code = by_code(doc)
|
||||
|
||||
@ -185,6 +185,28 @@ def test_layout_without_code_caption(rules: Rules) -> None:
|
||||
assert [column.period_date for column in layout.periods] == expected_dates
|
||||
|
||||
|
||||
def test_layout_two_row_header_without_code_caption_keeps_upper_row(rules: Rules) -> None:
|
||||
"""T13.I2: без подписи «Код» верхняя строка двухстрочного заголовка не теряется (st02): «Изменение, %» пропущена."""
|
||||
sheet = sheet_from_rows(
|
||||
[
|
||||
["Показатель", None, "На 31 декабря", "На 31 декабря", "Изменение, %"],
|
||||
[None, None, "2024 г.", "2023 г."],
|
||||
["Основные средства", "1150", 100, 90, 5],
|
||||
["Итого по разделу I", "1100", 100, 90, 5],
|
||||
["Запасы", "1210", 50, 40, 5],
|
||||
]
|
||||
)
|
||||
|
||||
layout = find_layout(sheet, rules, 1, sheet.n_rows)
|
||||
|
||||
assert layout.header_row == 1
|
||||
assert layout.first_data_row == 3 # noqa: PLR2004
|
||||
expected_dates = [datetime.date(2024, 12, 31), datetime.date(2023, 12, 31)]
|
||||
assert [column.period_date for column in layout.periods] == expected_dates
|
||||
assert [column.label for column in layout.periods] == ["На 31 декабря 2024 г.", "На 31 декабря 2023 г."]
|
||||
assert layout.skipped_labels == ("Изменение, %",)
|
||||
|
||||
|
||||
def test_layout_continuation_without_header(rules: Rules) -> None:
|
||||
sheet = sheet_from_rows(
|
||||
[
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user