89 lines
3.6 KiB
Python
89 lines
3.6 KiB
Python
"""Шаги 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)]
|