fix(statements): цель проверки присутствует в колонке только при наличии суммы; docstring порядка строк (M-1, M-3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
This commit is contained in:
parent
f9500d3f09
commit
e851445866
@ -1,10 +1,10 @@
|
||||
"""Логические проверки форм ББ/ОФР §7 в ядре (spec statement-checks; design D-4, D-5).
|
||||
|
||||
Перенос `tabular-parser/api/src/parser/statements/checks.py` по семантике: соотношение по каждой колонке, только если в
|
||||
колонке есть его цель; отсутствующий код и пустая сумма — 0; правая часть считается как записано и с `abs_terms` по
|
||||
модулю, выполнено, если хоть один вариант в пределах `tolerance`; жёсткое нарушение не больше
|
||||
`rounding_tolerance_percent` от наибольшего по модулю числа соотношения — мягкое; «сумма пятизначных подстрок =
|
||||
четырёхзначный родитель» — мягкая. Результат не хранится (DBML).
|
||||
Перенос `tabular-parser/api/src/parser/statements/checks.py` по семантике: соотношение по каждой колонке, только если
|
||||
в колонке есть его цель (есть сумма: пустая ячейка — цели нет); отсутствующий код и пустая сумма в правой части — 0;
|
||||
правая часть считается как записано и с `abs_terms` по модулю, выполнено, если хоть один вариант в пределах
|
||||
`tolerance`; жёсткое нарушение не больше `rounding_tolerance_percent` от наибольшего по модулю числа соотношения —
|
||||
мягкое; «сумма пятизначных подстрок = четырёхзначный родитель» — мягкая. Результат не хранится (DBML).
|
||||
"""
|
||||
|
||||
import functools
|
||||
@ -136,8 +136,9 @@ def _holds(amounts: Mapping[str, Decimal], rule: RelationRule, tolerance: Decima
|
||||
|
||||
|
||||
def _subline_rules(codes: Sequence[str], form: str) -> list[RelationRule]:
|
||||
"""«1110 = 11101 + …» для четырёхзначных кодов формы с пятизначными подстроками (по
|
||||
порядку строк, M-9 2b)."""
|
||||
"""«1110 = 11101 + …» для четырёхзначных кодов формы с пятизначными подстроками. Порядок строк в БД не
|
||||
хранится (DBML порядка не знает): «по порядку» — это порядок кодов, в каком их отдаёт `statement_amounts`
|
||||
(M-9 2b: подстрока относится к ближайшей четырёхзначной строке выше с теми же тремя цифрами)."""
|
||||
low, high = CODE_RANGES[form]
|
||||
parents = {code for code in codes if len(code) == _PARENT_LEN}
|
||||
children: dict[str, list[str]] = {}
|
||||
@ -177,14 +178,15 @@ def run_checks(
|
||||
column_indexes: Sequence[int],
|
||||
rules: CheckRules,
|
||||
) -> ChecksReport:
|
||||
"""Проверки §7 формы `form` по колонкам `column_indexes`; `lines` — `(код, {колонка:
|
||||
сумма})` по порядку строк."""
|
||||
"""Проверки §7 формы `form` по колонкам `column_indexes`; `lines` — `(код, {колонка: сумма})` в порядке кодов
|
||||
(порядок строк формы в БД не хранится; от него зависит только привязка пятизначных подстрок)."""
|
||||
explicit = list(rules.relations_for(form))
|
||||
sublines = _subline_rules([code for code, _ in lines], form) if rules.subline_check else []
|
||||
hard: list[FailedCheck] = []
|
||||
soft: list[FailedCheck] = []
|
||||
for column_index in column_indexes:
|
||||
amounts = {code: (values.get(column_index) or _ZERO) for code, values in lines}
|
||||
# код присутствует в колонке, только если в ней есть сумма (пустая ячейка — кода нет, как в tabular-parser)
|
||||
amounts = {code: amount for code, values in lines if (amount := values.get(column_index)) is not None}
|
||||
for rule in [*explicit, *sublines]:
|
||||
if rule not in sublines and rule.target not in amounts:
|
||||
continue
|
||||
|
||||
@ -116,7 +116,8 @@ async def delete_file_statements(session: AsyncSession, file_id: uuid.UUID) -> N
|
||||
async def statement_amounts(
|
||||
session: AsyncSession, meta_id: uuid.UUID
|
||||
) -> tuple[list[int], list[tuple[str, dict[int, Decimal | None]]]]:
|
||||
"""Индексы колонок и `(код строки, {колонка: сумма})` по возрастанию кода — вход `run_checks`."""
|
||||
"""Индексы колонок и `(код строки, {колонка: сумма})` по возрастанию кода — вход `run_checks`. Порядок строк
|
||||
формы в БД не хранится (DBML не хранит), поэтому порядок строк — лексикографический порядок кодов."""
|
||||
columns = (
|
||||
await session.execute(
|
||||
select(FinancialStatementColumn.id, FinancialStatementColumn.column_index)
|
||||
|
||||
@ -195,3 +195,12 @@ def test_broken_rules_file(tmp_path) -> None:
|
||||
bad.write_text("tolerance: 1\nrelations:\n - {form: BALANCE, relation: '16A0 = 1100', strict: true}\n", "utf-8")
|
||||
with pytest.raises(ValueError, match="statement_checks.yaml"):
|
||||
load_check_rules(bad)
|
||||
|
||||
|
||||
def test_target_with_empty_cell_in_column_is_skipped_but_zero_is_checked() -> None:
|
||||
"""M-1: цель присутствует в колонке, только если в ней есть сумма: пустая ячейка (`None`) — цели нет, соотношение
|
||||
пропущено (как в tabular-parser); явный 0 — цель есть, и при ненулевых слагаемых это нарушение."""
|
||||
terms = (("1100", {0: "300", 1: "300"}), ("1200", {0: "600", 1: "600"}))
|
||||
empty = run_checks("BALANCE", _lines(*terms, ("1600", {0: None, 1: "0"})), [0, 1], RULES)
|
||||
failed = _by_relation(empty.failed, "1600 = 1100 + 1200")
|
||||
assert [(item.column_index, item.expected, item.actual) for item in failed] == [(1, D("0"), D("900"))]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user