test(golden): тесты ядра под обновлённые golden fixtures (#33)

- test_mapping_golden: число снимков проверяется снизу (MIN_SNAPSHOTS = 113: rb 42, schastl 30, osv-dkb 22,
  synthetic 19), новые снимки fixtures проходят те же инварианты и не ломают тест; известные коды вне плана
  счетов — явный список KNOWN_OUT_OF_PLAN (60.АВ и 10 субсчетов 76 пакета ДКБ), новый код по-прежнему валит тест.
- test_consolidation_fixtures: UNMAPPED_TOTAL 1381 → 1379 — две строки свода РБ находят контрагента по ИНН из
  названия (tabular-parser #15).

Маппинг ядра без изменений: 113 снимков, 129 документов, 24 384 строки за ~7 с, все инварианты выполняются.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
This commit is contained in:
Andrey Epifancev 2026-10-07 05:18:54 +04:00
parent a64dbabb16
commit cac81d0318
2 changed files with 34 additions and 8 deletions

View File

@ -48,7 +48,9 @@ SCHASTL_FILE_NAME = "ПЕКАРНЯ СЧАСТЛИВОЧНАЯ" # в файла
RB_NAME_IN_SCHASTL = "Русский бисквит"
TIME_BUDGET_SECONDS = 60.0
Q1, Q2, Q3 = "Q1", "Q2", "Q3"
ROWS_TOTAL, UNMAPPED_TOTAL = 1385, 1381 # свод РБ 2024 Q3
# свод РБ 2024 Q3; 1379 = 1381 − 2: две строки находят контрагента по ИНН, извлечённому парсером из названия
# (tabular-parser #15, golden fixtures #8; issue #33)
ROWS_TOTAL, UNMAPPED_TOTAL = 1385, 1379
CHANGED_ROWS, PROPAGATED = 4, 17 # строк свода, получивших контрагента; записей osv_entry, кроме выбранной (18 всего)
SUMS = ("opening_debit", "opening_credit", "turnover_debit", "turnover_credit", "closing_debit", "closing_credit")

View File

@ -9,13 +9,16 @@
Инварианты: строк `osv_entry` = строк результата без `TOTAL`; у каждой строки уровня > 0 есть родитель (строка того же
документа с `row_number = parent_row_number`); шесть сумм равны снимку; `account_id` = строка плана счетов seed для
нормализованного кода, а для кодов вне плана — NULL (вне плана 1С остаётся только «60.АВ» синтетического снимка).
нормализованного кода, а для кодов вне плана — NULL (известные коды вне плана 1С — `KNOWN_OUT_OF_PLAN`).
Число снимков проверяется снизу (`MIN_SNAPSHOTS`), а не на равенство (issue #33): новые снимки fixtures не ломают тест,
а проходят те же инварианты; пропажа снимков по-прежнему ловится.
"""
import json
import time
import uuid
from collections import defaultdict
from collections import Counter, defaultdict
from datetime import UTC, datetime
from decimal import Decimal
from pathlib import Path
@ -37,7 +40,25 @@ from tests.factories import make_legal_entity, make_stored_file
from tests.fixtures_helpers import golden_result
GOLDEN_DIR = Path("golden") / "expected_osv_parse"
EXPECTED_SNAPSHOTS = 88 # результатов ОСВ в fixtures (rb, schastl, synthetic): 72 реальных + 16 синтетических
# результатов ОСВ в fixtures на 07.10.2026: rb 42, schastl 30, osv-dkb 22 (пакет ДКБ), synthetic 19 — всего 113
MIN_SNAPSHOTS = 113
# коды строк-счетов, которых нет в плане счетов seed (выгрузка 1С, issue #23): account_id у них NULL, корень счёта
# известен. Новый код вне плана валит тест — сигнал о дыре в плане счетов (follow-up arm-core #28)
KNOWN_OUT_OF_PLAN = frozenset(
{
"60.АВ", # synthetic/s09_subaccount_names
"76.07.4", # osv-dkb/npz
"76.09.1", # osv-dkb/nkz (76.09.1–76.09.4)
"76.09.2",
"76.09.3",
"76.09.4",
"76.77", # osv-dkb/primanych, osv-dkb/tsimlyansky (76.77, 76.77.1, .2, .5)
"76.77.1",
"76.77.2",
"76.77.5",
"76.АФ", # osv-dkb/primanych
}
)
TIME_BUDGET_SECONDS = 60.0 # tasks 3.4: маппинг всех снимков — не дольше минуты
SUM_FIELDS = ("opening_debit", "opening_credit", "turnover_debit", "turnover_credit", "closing_debit", "closing_credit")
TOTAL = "TOTAL"
@ -89,10 +110,12 @@ def _check_sheet( # noqa: PLR0912 - все инварианты одного л
async def test_all_golden_snapshots_mapped(seeded: AsyncSession, fixtures_dir: Path) -> None: # noqa: PLR0914
"""«Все golden-снимки парсера»: 88 результатов маппятся в БД, инварианты спеки на каждом документе, ≤ 60 с."""
"""«Все golden-снимки парсера» (не меньше 113) маппятся в БД, инварианты спеки на каждом документе, ≤ 60 с."""
session = seeded
snapshots = _snapshots(fixtures_dir)
assert len(snapshots) == EXPECTED_SNAPSHOTS
groups = Counter(path.relative_to(fixtures_dir / GOLDEN_DIR).parts[0] for path in snapshots)
by_group = dict(sorted(groups.items()))
assert len(snapshots) >= MIN_SNAPSHOTS, f"снимков {len(snapshots)} < {MIN_SNAPSHOTS}: {by_group}"
owner = await make_legal_entity(session, name="АО «Русский бисквит»", inn="3528052718")
chart = {
code: account_id for code, account_id in await session.execute(select(ChartOfAccount.code, ChartOfAccount.id))
@ -157,8 +180,9 @@ async def test_all_golden_snapshots_mapped(seeded: AsyncSession, fixtures_dir: P
f"{sorted(missing_codes)}"
)
assert problems == []
# план счетов seed = выгрузка 1С (issue #23): вне плана остаётся только «60.АВ» синтетического снимка s09
assert missing_codes <= {"60.АВ"}
# план счетов seed = выгрузка 1С (issue #23): вне плана — только известные коды (s09 и субсчета пакета ДКБ)
unknown = sorted(missing_codes - KNOWN_OUT_OF_PLAN)
assert not unknown, f"новые коды вне плана счетов: {unknown}"
assert with_kind > 0 # есть сопоставленные строки: проверка «kind ⇔ counterparty_id» не вырождена
assert with_account > 0 # строки со счётом плана счетов есть — сверка account_id не вырождена
assert mapping_seconds <= TIME_BUDGET_SECONDS