arm-tabular-parser/api/scripts/golden_update.py
Andrey Epifancev 9b8b8ea727 fix: финальное ревью — лимиты чтения xlsx (I-1), лог traceback (I-2), ACCOUNT_RE (M-1), ИНН/КПП (M-3)
- I-1 (R-23): readers/_limits.py — сумма ZipInfo.file_size ≤ 10 × PARSE_MAX_BYTES (параметр read_book от
  OsvParser), потоковый скан <mergeCell ref> до openpyxl и площадь merged после чтения любого формата
  (MERGED_AREA_LIMIT = 200 000; реальный максимум 10 090) → UNSUPPORTED_FORMAT; дедлайн в циклах detect
  (поиск строки групп, дополнительные колонки); README «Лимиты» исправлен, «Известные ограничения» (defusedxml).
- I-2: OsvParser логирует стек непредвиденной ошибки (loguru ERROR), UNREADABLE — WARNING без стека.
- M-1: ACCOUNT_RE с re.ASCII | re.DOTALL (многострочное название после запятой, только цифры 0–9).
- M-3 (R-24): правило детектора ИНН не дополняет 9/11 цифр нулём; дополнение — только при извлечении
  (numbers.inn_digits) из колонки, выбранной по заголовку или по строгому правилу.
- T9.1: тесты R-21 (свёрнутый родитель на противоположной стороне; (0,0) при детях (X, X)).
- T1.1: хелперы фикстур пропускают скрытые файлы/каталоги и lock-файлы Excel (~$…).
- T12.3: golden-update не удаляет снимки части без исходного каталога (osv/synthetic или реальные файлы).

Golden-снимки не изменились (82/82).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 16:29:53 +04:00

70 lines
3.5 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.

"""Перезапись golden-снимков разбора ОСВ и diff изменившихся (design D-10).
Из каталога `api/`: `ENV_FILE=.env.test .venv/bin/python scripts/golden_update.py [--check]` (то же — `make
golden-update`, из корня репозитория). Файлы — `fixtures/osv/**` включая `synthetic/`; снимки — в
`fixtures/golden/expected_osv_parse/` (зеркало `osv/`). Без флага снимки перезаписываются, для изменившихся печатается
unified diff; `--check` только сравнивает (код возврата 1, если снимки расходятся с разбором). Снимки части, для
которой нет исходного каталога (`osv/synthetic` отсутствует или пуст, нет реальных файлов), не удаляются.
Снимок — регрессия, а не оракул: перед коммитом в репозиторий fixtures просмотрите diff (`git -C ../fixtures diff`)
и убедитесь, что каждое изменение — следствие намеренной правки парсера.
"""
import argparse
import sys
from collections.abc import Sequence
from pathlib import Path
API_ROOT = Path(__file__).resolve().parents[1]
sys.path[:0] = [str(API_ROOT), str(API_ROOT / "tests")]
from fixtures_helpers import ( # noqa: E402
fixtures_skip_reason,
osv_fixture_files,
resolve_fixtures_dir,
synthetic_files,
)
from golden_helpers import sync_snapshots # noqa: E402
from src.parser import get_parser # noqa: E402
EXIT_OK = 0
EXIT_DIFFERS = 1
EXIT_NO_FIXTURES = 3
def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser(description="Golden-снимки разбора ОСВ: перезапись + diff, либо --check")
parser.add_argument("--check", action="store_true", help="только сравнить снимки с разбором, ничего не писать")
options = parser.parse_args(argv)
reason = fixtures_skip_reason()
if reason is not None:
print(reason, file=sys.stderr)
return EXIT_NO_FIXTURES
root = resolve_fixtures_dir()
files = [*osv_fixture_files(root), *synthetic_files(root)]
report = sync_snapshots(root, files, get_parser(), write=not options.check)
for diff in report.diffs:
sys.stdout.write(diff if diff.endswith("\n") else diff + "\n")
mode = "режим --check, ничего не записано" if options.check else "снимки записаны"
print(
f"файлов: {len(files)}; без изменений: {report.unchanged}; новых: {len(report.created)}; "
f"изменено: {len(report.changed)}; лишних снимков: {len(report.orphans)}; сохранено без исходного каталога: "
f"{len(report.protected)} ({mode})",
file=sys.stderr,
)
for orphan in report.orphans:
print(f"лишний снимок{'' if options.check else ' удалён'}: {orphan}", file=sys.stderr)
for kept in report.protected:
print(f"снимок сохранён (нет исходного каталога): {kept}", file=sys.stderr)
return EXIT_DIFFERS if options.check and report.differs else EXIT_OK
if __name__ == "__main__":
reconfigure = getattr(sys.stdout, "reconfigure", None)
if reconfigure is not None:
reconfigure(encoding="utf-8")
raise SystemExit(main())