feat(statement_checks): «родитель = Σ подстрок» не проверяется при пустой детализации (#118, AURORA-1527)
По колонке соотношение считается, только если хотя бы одна подстрока родителя имеет значение (amount не null; ноль — значение). Прочие соотношения и «пустая сумма = 0» не менялись. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
11b0216cff
commit
7ab16388f2
@ -568,7 +568,7 @@ S3 (storage); без S3 — предупреждение в логе, цикл
|
||||
|
||||
- **`GET /documents/{id}/statement`** — `{document, edition, header, columns[], lines[], checks}`: `header` — `inn`,
|
||||
`report_date`, `units` вида `{value, original_value, confidence, verified, modified}` (`confidence` есть у ИНН, у даты и единиц — `null`) и справочные поля формы
|
||||
(`organization_name`, `okud`, `okpo`, …); `lines[].values[]` — суммы строкой, `is_modified`, `original_amount`; `lines[].parent_code` — у пятизначной подстроки код её четырёхзначной строки-родителя (правило проверки «сумма подстрок = родитель», `domain/statement_checks.subline_parents`: ближайшая выше строка с теми же тремя цифрами, иначе строка с кодом из первых четырёх цифр; родитель считается по строкам, участвующим в проверках, — достроенные из справочника пустые строки родителями не бывают), у остальных строк `null`; поле вычисляется на лету, в БД не хранится; `checks` —
|
||||
(`organization_name`, `okud`, `okpo`, …); `lines[].values[]` — суммы строкой, `is_modified`, `original_amount`; `lines[].parent_code` — у пятизначной подстроки код её четырёхзначной строки-родителя (правило проверки «сумма подстрок = родитель», `domain/statement_checks.subline_parents`: ближайшая выше строка с теми же тремя цифрами, иначе строка с кодом из первых четырёх цифр; родитель считается по строкам, участвующим в проверках, — достроенные из справочника пустые строки родителями не бывают), у остальных строк `null`; поле вычисляется на лету, в БД не хранится; мягкая проверка «сумма подстрок = родитель» по колонке выполняется, только если хотя бы одна подстрока родителя в ней непустая (`amount` не `null`; ноль — значение), иначе не проверяется; `checks` —
|
||||
`{failed, soft_failed, cross_form, accepted}` (нарушение — `{column_index, relation, expected, actual}`, у межформенной
|
||||
проверки `column_index = -1`). Не форма (ОСВ, расшифровка, ББ/ОФР без данных формы — созданные до Среза 3b) — 409 `DOCUMENT_NOT_STATEMENT`.
|
||||
Подтверждённая форма читается. Форма достраивается по справочнику строк редакции (`api/rules/statement_lines.yaml`): недостающие строки (`id = null`, `origin = template`) и ячейки (`id = null`); в БД не пишется.
|
||||
|
||||
@ -5,7 +5,8 @@
|
||||
строки); отсутствующий код и пустая сумма в правой части — 0; правая часть считается как записано и с `abs_terms` по
|
||||
модулю, выполнено, если хоть один вариант в пределах `tolerance`; жёсткое нарушение не больше
|
||||
`rounding_tolerance_percent` от наибольшего по модулю числа соотношения — мягкое; «сумма пятизначных подстрок =
|
||||
четырёхзначный родитель» — мягкая. Результат не хранится (DBML).
|
||||
четырёхзначный родитель» — мягкая и только если в колонке есть хоть одна непустая подстрока (AURORA-1527).
|
||||
Результат не хранится (DBML).
|
||||
"""
|
||||
|
||||
import functools
|
||||
@ -199,9 +200,14 @@ def run_checks(
|
||||
# присутствие цели = наличие строки формы; пустая сумма (None) = 0, как `_amounts` в tabular-parser: оба
|
||||
# производителя отдают значение на каждую колонку каждой кодированной строки (пустая ячейка — amount=None)
|
||||
amounts = {code: (values.get(column_index) or _ZERO) for code, values in lines}
|
||||
filled = {code: values.get(column_index) is not None for code, values in lines}
|
||||
for rule in [*explicit, *sublines]:
|
||||
if rule not in sublines and rule.target not in amounts:
|
||||
continue
|
||||
# AURORA-1527: детализации нет (все подстроки родителя в колонке пусты, `None`) — «родитель = Σ подстрок»
|
||||
# по колонке не проверяется; ноль — значение. Правило кода, словарь не меняется.
|
||||
if rule in sublines and all(filled.get(code) is not True for _, code in rule.terms):
|
||||
continue
|
||||
holds, expected, actual = _holds(amounts, rule, rules.tolerance)
|
||||
if holds:
|
||||
continue
|
||||
|
||||
@ -213,6 +213,7 @@ async def test_get_statement_parent_code_matches_subline_check(
|
||||
"""Нарушенная «1110 = 11101 + …» строится по тем же подстрокам, что отдаёт `parent_code`."""
|
||||
document = await form(seeded, fixtures_dir, UNAGRANDE_BALANCE)
|
||||
await set_amount(seeded, document, "1110", Decimal("999999"), current=True)
|
||||
await set_amount(seeded, document, "11101", Decimal("1"), current=True) # детализация есть — проверка работает
|
||||
client = await auth_client(KA_RF)
|
||||
body = (await client.get(f"/documents/{document.id}/statement")).json()
|
||||
kids = sorted(line["line_code"] for line in body["lines"] if line["parent_code"] == "1110")
|
||||
@ -220,6 +221,17 @@ async def test_get_statement_parent_code_matches_subline_check(
|
||||
assert f"1110 = {' + '.join(kids)}" in {item["relation"] for item in body["checks"]["soft_failed"]}
|
||||
|
||||
|
||||
async def test_get_statement_no_subline_warnings_without_detail(
|
||||
seeded: AsyncSession, fixtures_dir: Path, auth_client: AuthClientFactory
|
||||
) -> None:
|
||||
"""AURORA-1527: все подстроки родителя пусты — «1150 = 11501 + …», «1170 = 11701», «1210 = 12101» не выдаются."""
|
||||
document = await form(seeded, fixtures_dir, UNAGRANDE_BALANCE)
|
||||
client = await auth_client(KA_RF)
|
||||
body = (await client.get(f"/documents/{document.id}/statement")).json()
|
||||
soft = [item["relation"] for item in body["checks"]["soft_failed"]]
|
||||
assert not any(relation.startswith(("1150 =", "1170 =", "1210 =")) for relation in soft), soft
|
||||
|
||||
|
||||
async def test_get_statement_orphan_subline_has_no_parent(
|
||||
seeded: AsyncSession, fixtures_dir: Path, auth_client: AuthClientFactory
|
||||
) -> None:
|
||||
|
||||
@ -245,3 +245,29 @@ def test_run_checks_and_subline_parents_agree() -> None:
|
||||
for parent in set(parents.values())
|
||||
}
|
||||
assert {item.relation for item in report.soft_failed} == expected
|
||||
|
||||
|
||||
def test_subline_check_skipped_when_all_sublines_empty_in_column() -> None:
|
||||
# AURORA-1527: детализации нет (все подстроки пусты) — «1150 = 11501 + …» по колонке не проверяется
|
||||
lines = _lines(("1150", {0: "90", 1: "90"}), ("11501", {0: None, 1: None}), ("11502", {0: None, 1: None}))
|
||||
assert _by_relation(run_checks("BALANCE", lines, [0, 1], RULES).soft_failed, "1150 = 11501 + 11502") == []
|
||||
|
||||
|
||||
def test_subline_check_is_per_column() -> None:
|
||||
# в колонке 0 подстроки пусты (не проверяется), в колонке 1 заполнены и не сходятся (нарушение)
|
||||
lines = _lines(("1150", {0: "90", 1: "90"}), ("11501", {0: None, 1: "10"}), ("11502", {0: None, 1: None}))
|
||||
failed = _by_relation(run_checks("BALANCE", lines, [0, 1], RULES).soft_failed, "1150 = 11501 + 11502")
|
||||
assert [(f.column_index, f.expected, f.actual) for f in failed] == [(1, D("90"), D("10"))]
|
||||
|
||||
|
||||
def test_subline_zero_is_a_value() -> None:
|
||||
# ноль — значение: подстроки с нулями проверяются как раньше
|
||||
lines = _lines(("1150", {0: "90"}), ("11501", {0: "0"}), ("11502", {0: None}))
|
||||
assert len(_by_relation(run_checks("BALANCE", lines, [0], RULES).soft_failed, "1150 = 11501 + 11502")) == 1
|
||||
|
||||
|
||||
def test_empty_sublines_do_not_affect_other_relations() -> None:
|
||||
# прочие соотношения §7 не затронуты: пустая цель 1200 при ненулевых слагаемых — по-прежнему жёсткое нарушение
|
||||
lines = _lines(("1210", {0: "5"}), ("12101", {0: None}), ("1200", {0: None}), ("1600", {0: "5"}))
|
||||
report = run_checks("BALANCE", lines, [0], RULES)
|
||||
assert [f.relation for f in report.failed if f.relation.startswith("1200 =")] != []
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user