Andrey Epifancev 412a322b9d chore(arm_statements): копия пакета arm-statements@6bce80b (#52)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:38:43 +04:00

89 lines
3.6 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.

"""Шаги 4–5: значения ячеек в строки формы и предупреждения единиц измерения."""
from collections.abc import Sequence
from decimal import Decimal
from arm_statements.amounts import build_value
from arm_statements.issues import WarningCode, make_warning
from arm_statements.loader import Rules
from arm_statements.model import (
LineStatus,
Source,
StatementHeader,
StatementLine,
StatementValue,
StatementWarning,
)
from arm_statements.rows import RawLine
CODE_CONFIDENCE_CAP = 99.0
_PERCENT = 100
def _code_confidence(raw: RawLine) -> float | None:
if raw.code_cell is None:
return None
confidence = raw.code_cell.confidence
return min(confidence, CODE_CONFIDENCE_CAP) if raw.code_fixed else confidence
def build_lines(
raw: Sequence[RawLine], source: Source, rules: Rules
) -> tuple[list[StatementLine], list[StatementWarning]]:
"""Строки формы со значениями; `VALUE_OUT_OF_RANGE` (`info`) на каждое значение вне диапазона."""
lines: list[StatementLine] = []
warnings: list[StatementWarning] = []
for item in raw:
values: list[StatementValue] = []
for column_index, cell in item.cells:
built = build_value(cell, column_index, source, rules, split_part=item.split)
values.append(built.value)
if built.out_of_range and cell is not None:
message = f"значение ячейки {cell.ref} вне диапазона numeric(18,2)"
warnings.append(
make_warning(
WarningCode.VALUE_OUT_OF_RANGE, message, rules, line_refs=(item.ref,), column_index=column_index
)
)
lines.append(
StatementLine(
row_index=item.row_index,
ref=item.ref,
line_status=item.line_status,
line_code=item.code if item.line_status is LineStatus.OK else None,
line_code_text=item.code_cell.text if item.code_cell is not None else None,
line_code_confidence=_code_confidence(item),
line_name=item.name.text if item.name is not None else None,
line_name_confidence=item.name.confidence if item.name is not None else None,
parent_ref=item.parent_ref,
needs_review=False,
values=tuple(values),
)
)
return lines, warnings
def _has_fraction(amount: Decimal) -> bool:
return amount != amount.to_integral_value()
def units_warnings(
lines: Sequence[StatementLine],
header: StatementHeader,
adapter_warnings: Sequence[StatementWarning],
rules: Rules,
) -> list[StatementWarning]:
"""Единицы «тыс. руб.» и доля дробных не ниже порога: `FRACTIONAL_IN_THOUSANDS` / `XLSB_SCALE_SUSPECT`."""
if header.units.value != rules.units.thousands:
return []
amounts = [v.amount for line in lines for v in line.values if v.amount is not None]
if not amounts:
return []
fractional = sum(1 for amount in amounts if _has_fraction(amount))
if fractional * _PERCENT < rules.units.fractional_share_percent * len(amounts):
return []
no_formats = any(w.code == WarningCode.XLSB_NO_FORMATS for w in adapter_warnings)
code = WarningCode.XLSB_SCALE_SUSPECT if no_formats else WarningCode.FRACTIONAL_IN_THOUSANDS
message = f"единицы «{rules.units.thousands}», но {fractional} из {len(amounts)} значений с дробной частью"
return [make_warning(code, message, rules)]