"""Шаги 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)]