Строки-показатели вне каскада иерархии (#58)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
ef07ced609
commit
33b32cbad6
@ -1,13 +1,16 @@
|
||||
"""Строки-показатели ОСВ (§1.3, AURORA-1613, AURORA-1661): метка вида суммы строки и привязка к строке данных."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
from dataclasses import dataclass
|
||||
|
||||
from src.contract.common import DataType
|
||||
from src.parser.deadline import Deadline
|
||||
from src.parser.detect import TableHeader
|
||||
from src.parser.grid import Sheet
|
||||
from src.parser.hierarchy import ROOT_LEVEL, BodyRow, Levels, resolve_levels
|
||||
from src.parser.rules import Rules, first_match, normalize_text
|
||||
|
||||
__all__ = ["RowLabel", "column_label", "is_indicator_total", "read_label", "row_data_type"]
|
||||
__all__ = ["RowLabel", "column_label", "is_indicator_total", "levels_with_indicators", "read_label", "row_data_type"]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@ -53,3 +56,26 @@ def is_indicator_total(sheet: Sheet, row: int, header: TableHeader, rules: Rules
|
||||
"""Итоговая строка-показатель за «Итого»: метка колонки «Показатели» не «БУ»."""
|
||||
label = column_label(sheet, row, header)
|
||||
return bool(label) and first_match(rules.data_types.indicator_column, label) != "BU"
|
||||
|
||||
|
||||
def levels_with_indicators(rows: Sequence[BodyRow], total_row: int | None, deadline: Deadline) -> Levels:
|
||||
"""Каскад иерархии без строк-показателей; показатель — ребёнок предыдущей строки данных, на уровень глубже
|
||||
(§1.3): так строки НУ/Кол./«В валюте» на одном outline-уровне со своей строкой не ломают дерево строк БУ.
|
||||
|
||||
Итоговые строки-показатели и показатель без строки данных выше — уровень 0 без родителя.
|
||||
"""
|
||||
levels = resolve_levels([item for item in rows if not item.indicator], total_row, deadline)
|
||||
by_row: dict[int, int] = {}
|
||||
parent: dict[int, int | None] = {}
|
||||
previous: int | None = None
|
||||
for item in rows:
|
||||
if not item.indicator:
|
||||
by_row[item.row], parent[item.row] = levels.by_row[item.row], levels.parent[item.row]
|
||||
if total_row is None or item.row < total_row:
|
||||
previous = item.row
|
||||
continue
|
||||
if previous is None or (total_row is not None and item.row >= total_row):
|
||||
by_row[item.row], parent[item.row] = ROOT_LEVEL, None
|
||||
else:
|
||||
by_row[item.row], parent[item.row] = by_row[previous] + 1, previous
|
||||
return Levels(by_row=by_row, parent=parent, strategy=levels.strategy)
|
||||
|
||||
@ -18,7 +18,6 @@ from decimal import Decimal
|
||||
from typing import Literal
|
||||
|
||||
from src.contract.result import OsvGroup
|
||||
from src.parser.classify import row_data_type
|
||||
from src.parser.grid import Cell, Sheet
|
||||
from src.parser.header import InnHint
|
||||
from src.parser.hierarchy import BodyRow, Levels
|
||||
@ -104,7 +103,6 @@ def _build( # noqa: PLR0913, PLR0917 - всё нужное для вида ст
|
||||
hint: InnHint | None,
|
||||
rules: Rules,
|
||||
) -> _Tree:
|
||||
currency = frozenset(rules.data_types.currency_codes)
|
||||
children: dict[int | None, list[int]] = {}
|
||||
for item in rows:
|
||||
children.setdefault(levels.parent[item.row], []).append(item.row)
|
||||
@ -112,7 +110,7 @@ def _build( # noqa: PLR0913, PLR0917 - всё нужное для вида ст
|
||||
for siblings in children.values():
|
||||
tree.position.update((row, index) for index, row in enumerate(siblings))
|
||||
for item in rows:
|
||||
tree.kind[item.row] = _kind(item, total_row, sheet, hint, rules, currency, tree)
|
||||
tree.kind[item.row] = _kind(item, total_row, sheet, hint, rules, tree)
|
||||
if hint is not None and hint.direction == "below":
|
||||
# «ИНН не указан» (R-1 #32): с подсказкой «под» пустая строка-ребёнок наименования — строка ИНН без значения
|
||||
for item in rows:
|
||||
@ -129,14 +127,13 @@ def _kind( # noqa: PLR0913, PLR0917 - вид строки зависит от
|
||||
sheet: Sheet,
|
||||
hint: InnHint | None,
|
||||
rules: Rules,
|
||||
currency: frozenset[str],
|
||||
tree: _Tree,
|
||||
) -> Kind:
|
||||
if total_row is not None and item.row >= total_row:
|
||||
return "total"
|
||||
if item.is_account:
|
||||
return "account"
|
||||
if row_data_type(item.text, rules, currency) is not None:
|
||||
if item.indicator:
|
||||
return "indicator"
|
||||
found = row_inn(_label_cell(sheet, item), rules)
|
||||
if found is None:
|
||||
|
||||
@ -19,7 +19,7 @@ from src.parser.detect import TableHeader, find_table_header
|
||||
from src.parser.errors import InvalidFormatError
|
||||
from src.parser.grid import Sheet, column_letter
|
||||
from src.parser.header import ACCOUNT_NOT_FOUND_WARNING, DocHeader, build_group_levels, parse_doc_header
|
||||
from src.parser.hierarchy import resolve_levels
|
||||
from src.parser.indicators import levels_with_indicators
|
||||
from src.parser.inn_rows import InnGlue, glue_inn_rows
|
||||
from src.parser.osv_warnings import (
|
||||
OSV_HEADER,
|
||||
@ -181,7 +181,7 @@ class OsvParser:
|
||||
step = "строки таблицы"
|
||||
body, total_row, body_warnings = collect_body_rows(sheet, header, rules, deadline)
|
||||
step = "иерархия строк"
|
||||
levels = resolve_levels(body, total_row, deadline)
|
||||
levels = levels_with_indicators(body, total_row, deadline)
|
||||
step = "строки ИНН"
|
||||
glue = glue_inn_rows(
|
||||
body,
|
||||
|
||||
@ -1,11 +1,15 @@
|
||||
"""Метки строк-показателей (§1.3, #58): колонка «Показатели» и текст строки."""
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
from gridbuilder import L, M, at, sheet_from_rows
|
||||
|
||||
from src.parser.deadline import Deadline
|
||||
from src.parser.detect import TableHeader, find_table_header
|
||||
from src.parser.grid import Sheet
|
||||
from src.parser.indicators import RowLabel, is_indicator_total, read_label
|
||||
from src.parser.hierarchy import BodyRow
|
||||
from src.parser.indicators import RowLabel, is_indicator_total, levels_with_indicators, read_label
|
||||
from src.parser.rules import Rules, load_rules
|
||||
|
||||
TITLE = "Оборотно-сальдовая ведомость по счету 60 за 1 квартал 2025 г."
|
||||
@ -80,3 +84,54 @@ def test_is_indicator_total(rules: Rules) -> None:
|
||||
assert is_indicator_total(sheet, FIRST_ROW, header, rules)
|
||||
assert not is_indicator_total(sheet, FIRST_ROW + 1, header, rules)
|
||||
assert not is_indicator_total(sheet, FIRST_ROW + 2, header, rules)
|
||||
|
||||
|
||||
# --- Иерархия: показатели вне каскада ---
|
||||
|
||||
|
||||
def _row(row: int, text: str, outline: int, *, indicator: bool = False) -> BodyRow:
|
||||
return BodyRow(
|
||||
row=row,
|
||||
text=text,
|
||||
is_account=text.isdigit(),
|
||||
account_rank=1 if text.isdigit() else None,
|
||||
bold=False,
|
||||
indent=0,
|
||||
outline=outline,
|
||||
sums=(Decimal(1),) * 6,
|
||||
indicator=indicator,
|
||||
)
|
||||
|
||||
|
||||
def test_indicators_are_children_of_previous_data_row_outside_cascade() -> None:
|
||||
rows = [
|
||||
_row(10, "60", 0),
|
||||
_row(11, "НУ", 0, indicator=True),
|
||||
_row(12, "Объект", 1),
|
||||
_row(13, "НУ", 1, indicator=True),
|
||||
_row(14, "Итого", 0),
|
||||
_row(15, "НУ", 0, indicator=True),
|
||||
]
|
||||
levels = levels_with_indicators(rows, 14, Deadline(3600))
|
||||
assert levels.strategy == "outline"
|
||||
assert levels.by_row == {10: 0, 11: 1, 12: 1, 13: 2, 14: 0, 15: 0}
|
||||
assert levels.parent == {10: None, 11: 10, 12: 10, 13: 12, 14: None, 15: None}
|
||||
|
||||
|
||||
def test_indicator_first_in_table_is_root() -> None:
|
||||
rows = [_row(10, "НУ", 0, indicator=True), _row(11, "60", 0)]
|
||||
levels = levels_with_indicators(rows, None, Deadline(3600))
|
||||
assert levels.by_row[10] == 0
|
||||
assert levels.parent[10] is None
|
||||
|
||||
|
||||
def test_levels_keep_sheet_row_order() -> None:
|
||||
rows = [
|
||||
_row(10, "60", 0),
|
||||
_row(11, "НУ", 0, indicator=True),
|
||||
_row(12, "Объект", 1),
|
||||
_row(13, "НУ", 1, indicator=True),
|
||||
]
|
||||
levels = levels_with_indicators(rows, None, Deadline(3600))
|
||||
assert list(levels.by_row) == [10, 11, 12, 13]
|
||||
assert list(levels.parent) == [10, 11, 12, 13]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user