Заголовок ОСВ: колонка «Показатели» (#58)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
af70c4c466
commit
43653f1b6f
@ -122,6 +122,7 @@ class TableHeader:
|
|||||||
right_edge: int # последняя колонка таблицы (правый край последней колонки сумм); правее — не таблица
|
right_edge: int # последняя колонка таблицы (правый край последней колонки сумм); правее — не таблица
|
||||||
extra_cols: dict[int, str] # колонка → заголовок в блоке (или буква колонки, если заголовка нет)
|
extra_cols: dict[int, str] # колонка → заголовок в блоке (или буква колонки, если заголовка нет)
|
||||||
group_label_rows: list[int] # строки блока, где в text_col начинается текст группировки («Счет», «Контрагенты»)
|
group_label_rows: list[int] # строки блока, где в text_col начинается текст группировки («Счет», «Контрагенты»)
|
||||||
|
indicator_col: int | None = None # колонка «Показатели» — метка вида суммы строки, §1.3
|
||||||
|
|
||||||
|
|
||||||
# --- Текст ячеек ---
|
# --- Текст ячеек ---
|
||||||
@ -381,13 +382,20 @@ def inn_from_cell(cell: Cell) -> str | None:
|
|||||||
return digits if digits.isdigit() and len(digits) in INN_LENGTHS else None
|
return digits if digits.isdigit() and len(digits) in INN_LENGTHS else None
|
||||||
|
|
||||||
|
|
||||||
def _inn_col(sheet: Sheet, block: range, text_col: int, first_sum: int, rules: Rules) -> int | None:
|
def _service_col(sheet: Sheet, block: range, text_col: int, first_sum: int, patterns: Iterable[Pattern]) -> int | None:
|
||||||
"""Колонка между текстом и суммами: по заголовку «ИНН» в блоке, иначе по доле значений из 10/12 цифр."""
|
"""Первая колонка между текстом и суммами, чей нормализованный заголовок в блоке матчит `patterns`."""
|
||||||
inn_patterns = rules.headers.service_columns["inn"]
|
|
||||||
for row in block:
|
for row in block:
|
||||||
for col, text in _texts(sheet, row):
|
for col, text in _texts(sheet, row):
|
||||||
if text_col < col < first_sum and _search(inn_patterns, text):
|
if text_col < col < first_sum and _search(patterns, text):
|
||||||
return col
|
return col
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _inn_col(sheet: Sheet, block: range, text_col: int, first_sum: int, rules: Rules) -> int | None:
|
||||||
|
"""Колонка между текстом и суммами: по заголовку «ИНН» в блоке, иначе по доле значений из 10/12 цифр."""
|
||||||
|
by_title = _service_col(sheet, block, text_col, first_sum, rules.headers.service_columns["inn"])
|
||||||
|
if by_title is not None:
|
||||||
|
return by_title
|
||||||
values: dict[int, list[Cell]] = {}
|
values: dict[int, list[Cell]] = {}
|
||||||
for row in _data_rows(sheet, block[-1], INN_SAMPLE_ROWS):
|
for row in _data_rows(sheet, block[-1], INN_SAMPLE_ROWS):
|
||||||
for col, cell in sheet.row_cells(row):
|
for col, cell in sheet.row_cells(row):
|
||||||
@ -465,6 +473,8 @@ def find_table_header(sheet: Sheet, rules: Rules, deadline: Deadline | None = No
|
|||||||
last_row = _block_last_row(sheet, group_row, sum_cells, rules)
|
last_row = _block_last_row(sheet, group_row, sum_cells, rules)
|
||||||
text_col = _text_col(sheet, group_row, last_row, first_sum)
|
text_col = _text_col(sheet, group_row, last_row, first_sum)
|
||||||
inn_col = _inn_col(sheet, range(group_row, last_row + 1), text_col, first_sum, rules)
|
inn_col = _inn_col(sheet, range(group_row, last_row + 1), text_col, first_sum, rules)
|
||||||
|
indicator_patterns = rules.headers.service_columns.get("indicator", [])
|
||||||
|
indicator_col = _service_col(sheet, range(group_row, last_row + 1), text_col, first_sum, indicator_patterns)
|
||||||
|
|
||||||
partial = TableHeader(group_row, last_row, columns, text_col, inn_col, right_edge, {}, [])
|
partial = TableHeader(group_row, last_row, columns, text_col, inn_col, right_edge, {}, [])
|
||||||
occupied = {*sum_cols, text_col} | ({inn_col} if inn_col is not None else set())
|
occupied = {*sum_cols, text_col} | ({inn_col} if inn_col is not None else set())
|
||||||
@ -485,6 +495,7 @@ def find_table_header(sheet: Sheet, rules: Rules, deadline: Deadline | None = No
|
|||||||
right_edge=right_edge,
|
right_edge=right_edge,
|
||||||
extra_cols=dict(sorted(extra_cols.items())),
|
extra_cols=dict(sorted(extra_cols.items())),
|
||||||
group_label_rows=_group_label_rows(sheet, group_row, last_row, text_col, rules),
|
group_label_rows=_group_label_rows(sheet, group_row, last_row, text_col, rules),
|
||||||
|
indicator_col=indicator_col,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -438,6 +438,36 @@ def test_header_without_text_column_left_of_sums_is_invalid_format(rules: Rules)
|
|||||||
# --- ИНН и extra ---
|
# --- ИНН и extra ---
|
||||||
|
|
||||||
|
|
||||||
|
def test_indicator_column_found_by_hyphenated_title(rules: Rules) -> None:
|
||||||
|
sheet = sheet_from_rows(
|
||||||
|
[
|
||||||
|
[TITLE],
|
||||||
|
["Счет", None, M(OPENING, 2), M(TURNOVER, 2), M(CLOSING, 2)],
|
||||||
|
["Счет", "Показа-\nтели", "Дебет", "Кредит", "Дебет", "Кредит", "Дебет", "Кредит"],
|
||||||
|
[L(0), *at({1: "60", 2: "Дебет", 3: 10, 8: 10})],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
header = find_table_header(sheet, rules)
|
||||||
|
|
||||||
|
assert [header.indicator_col] == [2]
|
||||||
|
assert [header.text_col] == [1]
|
||||||
|
assert list(header.extra_cols) == [2]
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_indicator_column_when_title_absent(rules: Rules) -> None:
|
||||||
|
sheet = sheet_from_rows(
|
||||||
|
[
|
||||||
|
[TITLE],
|
||||||
|
["Счет", M(OPENING, 2), M(TURNOVER, 2), M(CLOSING, 2)],
|
||||||
|
["Контрагенты", "Дебет", "Кредит", "Дебет", "Кредит", "Дебет", "Кредит"],
|
||||||
|
[L(0), *at({1: "60", 2: 10, 7: 10})],
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert find_table_header(sheet, rules).indicator_col is None
|
||||||
|
|
||||||
|
|
||||||
def test_kpp_between_inn_and_sums_goes_to_extra(rules: Rules) -> None:
|
def test_kpp_between_inn_and_sums_goes_to_extra(rules: Rules) -> None:
|
||||||
sheet = sheet_from_rows(
|
sheet = sheet_from_rows(
|
||||||
[
|
[
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user