chore(statements): комментарии, README, типы, тесты по итогам финального ревью (M-1, M-2, M-3, M-5, M-7, M-9, T2.1, T4.5, T10.1, T11.1)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
This commit is contained in:
parent
16b53b0bdb
commit
1ab7f2238d
@ -262,6 +262,7 @@ def _log_lost_values() -> None:
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Откат v5.19. Нужно онлайн-соединение (проверки читают данные): offline-режим `--sql` не поддерживается (T2.1)."""
|
||||
_check_downgrade_allowed()
|
||||
_log_lost_values()
|
||||
op.execute(f"DROP TABLE {S}.document_issue")
|
||||
|
||||
@ -51,13 +51,14 @@ class NameCheckLine:
|
||||
|
||||
def name_mismatches(lines: Iterable[NameCheckLine], catalog: LineCatalog, form: str) -> list[Hashable]:
|
||||
"""Ключи строк `ok` с неуверенным кодом (`line_code_confidence < 100`), без закреплённого кода (`code_fixed`),
|
||||
чьё наименование не совпало ни с одним справочным именем кода (Р-16). Кода нет в словаре — несовпадение."""
|
||||
чьё наименование не совпало ни с одним справочным именем кода (Р-16). Кода нет в словаре — несовпадение.
|
||||
Строка без уверенности (`None`) считается уверенной и не проверяется: спека требует `confidence < 100`."""
|
||||
result: list[Hashable] = []
|
||||
for line in lines:
|
||||
if line.line_status != StatementLineStatus.ok or line.code_fixed:
|
||||
continue
|
||||
confidence = line.line_code_confidence
|
||||
if confidence is None or confidence >= _CONFIDENT:
|
||||
if confidence is None or confidence >= _CONFIDENT: # None — уверенной (T10.1)
|
||||
continue
|
||||
if not line.line_code or not names_match(line.line_name, catalog.names(form, line.line_code)):
|
||||
result.append(line.key)
|
||||
|
||||
@ -10,6 +10,7 @@ from typing import Any
|
||||
|
||||
from sqlalchemy import delete, exists, func, insert, or_, select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.sql.expression import ScalarSelect
|
||||
|
||||
from src.domain.statement_aggregate import AggLine, CodeAggregate, aggregate_by_code
|
||||
from src.domain.statement_issues import IssueDraft
|
||||
@ -213,7 +214,7 @@ async def delete_documents_statements(session: AsyncSession, document_ids: Seque
|
||||
await _delete_statements(session, list(document_ids))
|
||||
|
||||
|
||||
async def _delete_statements(session: AsyncSession, documents: Any) -> None:
|
||||
async def _delete_statements(session: AsyncSession, documents: ScalarSelect[uuid.UUID] | list[uuid.UUID]) -> None:
|
||||
await session.execute(delete(DocumentIssue).where(DocumentIssue.document_id.in_(documents)))
|
||||
metas = select(FinancialStatementMeta.id).where(FinancialStatementMeta.document_id.in_(documents)).scalar_subquery()
|
||||
lines = select(FinancialStatementLine.id).where(FinancialStatementLine.meta_id.in_(metas)).scalar_subquery()
|
||||
|
||||
@ -259,3 +259,42 @@ async def test_manual_confirm_of_auto_confirmed_is_409(seeded: AsyncSession, aut
|
||||
assert response.status_code == HTTP_CONFLICT, response.text
|
||||
assert response.json()["detail"]["code"] == "ALREADY_CONFIRMED"
|
||||
assert len(await _confirm_events(seeded)) == 1
|
||||
|
||||
|
||||
async def _synthetic_disclosure(session: AsyncSession, fixtures_dir: Path, *, office: RegionalOffice) -> Document:
|
||||
"""RECOGNIZED `DISCLOSURE` из синтетического снимка st06 (T11.1: форм-проверки к не-форме не применяются)."""
|
||||
import json # noqa: PLC0415 - нужен только здесь
|
||||
|
||||
path = fixtures_dir / GOLDEN_STATEMENTS / "synthetic" / "st06_header_variants.json"
|
||||
data = json.loads(path.read_text("utf-8"))
|
||||
inn = next((d["header"].get("inn") for d in data["documents"] if d.get("header", {}).get("inn")), None)
|
||||
owner = await make_legal_entity(session, name="ООО Синтетика", inn=inn, office=office)
|
||||
documents = await map_golden_snapshot(session, entity=owner, path=path, uploaded_by=None)
|
||||
disclosure = next(d for d in documents if d.document_type is DocumentType.DISCLOSURE)
|
||||
assert disclosure.status is DocumentStatus.RECOGNIZED
|
||||
return disclosure
|
||||
|
||||
|
||||
@pytest.mark.fixtures
|
||||
async def test_recognized_disclosure_is_auto_confirmed(seeded: AsyncSession, fixtures_dir: Path) -> None:
|
||||
office = (await seeded.execute(select(RegionalOffice).where(RegionalOffice.name == ROSTOV))).scalar_one()
|
||||
disclosure = await _synthetic_disclosure(seeded, fixtures_dir, office=office)
|
||||
file = await seeded.get(StoredFile, disclosure.stored_file_id)
|
||||
assert file is not None
|
||||
|
||||
assert await auto_confirm(seeded, file=file, documents=[disclosure], now=utc_now()) == [disclosure]
|
||||
assert disclosure.status is DocumentStatus.CONFIRMED
|
||||
|
||||
|
||||
@pytest.mark.fixtures
|
||||
async def test_recognized_disclosure_is_manually_confirmable(
|
||||
seeded: AsyncSession, fixtures_dir: Path, auth_client: AuthClientFactory
|
||||
) -> None:
|
||||
office = (await seeded.execute(select(RegionalOffice).where(RegionalOffice.name == ROSTOV))).scalar_one()
|
||||
disclosure = await _synthetic_disclosure(seeded, fixtures_dir, office=office)
|
||||
client = await auth_client(KA_RF)
|
||||
|
||||
response = await client.post(f"/documents/{disclosure.id}/confirm")
|
||||
|
||||
assert response.status_code == 200, response.text # noqa: PLR2004
|
||||
assert response.json()["status"] == "CONFIRMED"
|
||||
|
||||
@ -120,3 +120,8 @@ def test_name_mismatches() -> None:
|
||||
_line(10, None, None, "Без кода", status="unreadable"),
|
||||
]
|
||||
assert name_mismatches(lines, CATALOG, "BALANCE") == [1, 3, 8, 9]
|
||||
|
||||
|
||||
def test_name_mismatches_missing_confidence_is_confident() -> None:
|
||||
"""T10.1: `line_code_confidence = None` не даёт `NAME_MISMATCH` (спека: `confidence < 100`)."""
|
||||
assert name_mismatches([_line(1, "1120", None, "Чужое имя")], CATALOG, "BALANCE") == []
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user