This repository has been archived on 2026-10-26. You can view files and clone it, but cannot push or open issues or pull requests.
2026-10-18 14:28:20 +04:00

179 lines
6.4 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.

"""Точка входа пересчёта формы по сохранённым данным (спека «Точка входа пакета для пересчёта…»).
Ядро собирает `StoredForm` из своих строк БД; сервисы функцией не пользуются. Пересчёт не меняет `line_status`,
суммы и коды — только выводит признаки (Р-34: подтверждение кода — `code_fixed`, уверенность кода не меняется).
"""
from collections.abc import Mapping, Sequence
from dataclasses import dataclass
from datetime import date
from decimal import Decimal
from arm_statements.checks import checks_from
from arm_statements.evaluation import EvalLine, EvalValue
from arm_statements.loader import Rules, default_rules
from arm_statements.model import (
DocStatus,
DocumentType,
Edition,
FormVariant,
LineStatus,
PeriodKind,
Severity,
StatementChecks,
)
from arm_statements.review import evaluate
from arm_statements.status import StatusInput, StatusLine, StatusValue, document_status
from arm_statements.structure import edition_by_date
@dataclass(frozen=True, slots=True)
class StoredColumn:
key: str
column_index: int | None
period_date: date | None
period_kind: PeriodKind
@dataclass(frozen=True, slots=True)
class StoredValue:
column_key: str
amount: Decimal | None
text: str | None # source_text; у значения правки None
confidence: float | None
is_recognized: bool
value_fixed: bool # is_modified или is_confirmed
@dataclass(frozen=True, slots=True)
class StoredLine:
key: str
row_index: int | None # None — строка правки
line_status: LineStatus
line_code: str | None
line_code_confidence: float | None
line_name: str | None
code_fixed: bool # line_code_modified или is_confirmed строки
parent_key: str | None
values: tuple[StoredValue, ...]
@dataclass(frozen=True, slots=True)
class StoredWarning:
code: str
severity: Severity
@dataclass(frozen=True, slots=True)
class StoredForm:
document_type: DocumentType
effective_date: date | None
has_organization_or_inn: bool
period_known: bool
needs_review_fields: tuple[str, ...]
accepted: bool
warnings: tuple[StoredWarning, ...]
columns: tuple[StoredColumn, ...]
lines: tuple[StoredLine, ...]
@dataclass(frozen=True, slots=True)
class RecomputeResult:
form_variant: FormVariant | None
edition: Edition | None
checks: StatementChecks
line_needs_review: Mapping[str, bool]
value_needs_review: Mapping[tuple[str, str], bool]
detail_parents: Mapping[str, str | None]
status: DocStatus
def _sort_key(line: StoredLine) -> tuple[bool, int]:
return (line.row_index is None, line.row_index or 0)
def _eval_line(line: StoredLine) -> EvalLine:
return EvalLine(
key=line.key,
order=line.row_index,
line_status=line.line_status,
line_code=line.line_code,
code_confidence=line.line_code_confidence,
code_fixed=line.code_fixed,
values=tuple(
EvalValue(v.column_key, v.amount, v.text, v.confidence, v.is_recognized, fixed=v.value_fixed)
for v in line.values
),
)
def _detail_parents(lines: Sequence[StoredLine]) -> dict[str, str | None]:
"""Ближайшая выше по `row_index` строка `ok` с `row_index`; у `detail` без `row_index` и без такой — `None`."""
parents: dict[str, str | None] = {}
last_ok: str | None = None
for line in lines: # уже отсортированы: строки правки в конце
if line.row_index is None:
if line.line_status is LineStatus.DETAIL:
parents[line.key] = None
continue
if line.line_status is LineStatus.DETAIL:
parents[line.key] = last_ok
elif line.line_status is LineStatus.OK:
last_ok = line.key
return parents
def recompute(form: StoredForm, rules: Rules | None = None) -> RecomputeResult:
"""Шаги 6–8 по сохранённым данным: вариант, редакция, §7, `needs_review`, родители `detail`, статус."""
rules = rules if rules is not None else default_rules()
if form.document_type is DocumentType.DISCLOSURE:
ordered = sorted(form.lines, key=_sort_key)
return RecomputeResult(
form_variant=None,
edition=edition_by_date(form.effective_date),
checks=StatementChecks((), ()),
line_needs_review={line.key: False for line in ordered},
value_needs_review={(line.key, v.column_key): False for line in ordered for v in line.values},
detail_parents=_detail_parents(ordered),
status=DocStatus.DONE,
)
lines = sorted(form.lines, key=_sort_key)
evaluation = evaluate(
[_eval_line(line) for line in lines],
[column.key for column in form.columns],
form.document_type,
form.effective_date,
rules,
accepted=form.accepted,
)
checks = checks_from(evaluation.outcomes, {column.key: column.column_index for column in form.columns})
status = document_status(
StatusInput(
document_type=form.document_type,
layout_found=any(line.row_index is not None for line in lines),
has_organization_or_inn=form.has_organization_or_inn,
period_known=form.period_known,
needs_review_fields=form.needs_review_fields,
lines=tuple(
StatusLine(line.line_status, evaluation.line_needs_review.get(line.key, False)) for line in lines
),
values=tuple(
StatusValue(line.line_status, evaluation.value_needs_review.get((line.key, v.column_key), False))
for line in lines
for v in line.values
),
hard_failures=evaluation.hard_failures,
accepted=form.accepted,
warning_severities=tuple(w.severity for w in form.warnings),
)
)
return RecomputeResult(
form_variant=evaluation.variant,
edition=evaluation.edition,
checks=checks,
line_needs_review=evaluation.line_needs_review,
value_needs_review=evaluation.value_needs_review,
detail_parents=_detail_parents(lines),
status=status,
)