2026-10-19 13:18:23 +04:00

88 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Строки-показатели ОСВ (§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", "levels_with_indicators", "read_label", "row_data_type"]
@dataclass(frozen=True)
class RowLabel:
"""Вид строки по метке: показатель ли она, `data_type` метки, текст строки взамен исходного, предупреждение."""
indicator: bool
data_type: DataType | None
text: str | None = None # для метки колонки «Показатели» — сама метка (`row_value`)
warning: str | None = None
def row_data_type(text: str, rules: Rules, currency_codes: frozenset[str]) -> DataType | None:
"""CURRENCY/QUANTITY по словарю строк («Вал.», «Кол.») или коду валюты ISO («USD», «usd»); иначе `None`."""
found = first_match(rules.data_types.row, text)
if found is not None:
return found
return "CURRENCY" if normalize_text(text).upper() in currency_codes else None
def column_label(sheet: Sheet, row: int, header: TableHeader) -> str:
"""Метка строки в колонке «Показатели» (пусто — колонки нет или ячейка пуста)."""
return "" if header.indicator_col is None else sheet.text_at(row, header.indicator_col)
def read_label(sheet: Sheet, row: int, text: str, header: TableHeader, rules: Rules) -> RowLabel:
"""Метка колонки «Показатели» важнее текста строки: «БУ» — строка данных BU, прочие — показатели; без метки —
показатель по тексту («Вал.», «Кол.», «Кол-во», «В валюте», код валюты), иначе обычная строка без своего вида."""
label = column_label(sheet, row, header)
if label:
data_type = first_match(rules.data_types.indicator_column, label)
if data_type == "BU":
return RowLabel(indicator=False, data_type="BU")
if data_type is not None or any(p.search(label) for p in rules.data_types.indicator_column_untyped):
return RowLabel(indicator=True, data_type=data_type, text=label)
warning = (
f"строка {row}: метка «{label}» в колонке «Показатели» не распознана — строка не участвует в проверках"
)
return RowLabel(indicator=True, data_type=None, text=label, warning=warning)
data_type = row_data_type(text, rules, frozenset(rules.data_types.currency_codes))
return RowLabel(indicator=data_type is not None, data_type=data_type)
def is_indicator_total(sheet: Sheet, row: int, header: TableHeader, rules: Rules) -> bool:
"""Итоговая строка-показатель за «Итого»: метка колонки «Показатели» не «БУ»."""
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, *, header_account: bool = False
) -> Levels:
"""Каскад иерархии без строк-показателей; показатель — ребёнок предыдущей строки данных, на уровень глубже
(§1.3): так строки НУ/Кол./«В валюте» на одном outline-уровне со своей строкой не ломают дерево строк БУ.
Итоговые строки-показатели и показатель без строки данных выше — уровень 0 без родителя.
"""
levels = resolve_levels(
[item for item in rows if not item.indicator], total_row, deadline, header_account=header_account
)
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)