EXPECTED_FILES 66 → 72, порог покрытия словарей, имя теста без числа, CLAUDE.md. Снимки — arm-ka-fixtures#7. make test-fixtures: 873 passed (ошибка только у roundtrip без NATS). Refs #10 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
10 KiB
CLAUDE.md — tabular-parser
Общий контекст системы — ../CLAUDE.md и ../docs/. Этот файл — только про этот сервис.
Что это
Stateless-сервис разбора XLS/XLSX/XLSB: ОСВ по счетам (§1 правил) и ББ/ОФР из Excel (§12). Вход — NATS
recognition.tabular.request + файл в S3 uploads/; выход — JSON по ../docs/contracts/recognition-result.schema.json
в S3 results/ + NATS recognition.result. Нет доступа к БД. Не знает про организации, периметры, план счетов —
возвращает структуру файла; нормализация и резолв — в arm-core.
Главное правило
Структура определяется динамически по содержимому (§1.0 правил): никаких координат ячеек, имён листов, букв колонок
в коде. Словари синонимов — rules/*.yaml. Фикстуры ../fixtures/osv/** — регрессия, не спецификация формата:
новый формат = новая фикстура + синоним, не новый if.
Тесты
- property-тесты инвариантов (§1.4) на всех файлах
../fixtures/osv/**и../fixtures/statements-xlsx/**; - снимки
../fixtures/golden/expected_osv_parse/*.json— после ручной сверки «Итого»; - синтетика только для вариантов, которых нет в фикстурах (шапка вариант 2, Вал./Кол., xlsb, без outline level).
Соглашения шаблона
Тот же шаблон команды, что у arm-core («Шаблон FastAPI», RSHB/DKB_ARMKA@ba172f8); общие соглашения —
../docs/context/03-architecture.md, «Соглашения шаблона команды». Репозиторий приведён к сервису без фронта и без БД
(change slice-0-skeleton). Команды backend — из api/, make — из корня репозитория.
Оставлено от шаблона: api/run_server.py (точка входа платформы, SERVER__HOST/PORT), api/src/app.py (lifespan:
подключение к MinIO и NATS, ошибка подключения логируется и не мешает старту; GET /healthcheck, gzip/proxy middleware),
core/config.py (pydantic-settings, SERVICE_NAME — только service.name конвертов (queue group — константа в core/nats.py), OPENBAO__MINIO__*
включая MINIO_SECURE, OPENBAO__NATS__*, DEBUG, ENV_FILE, NATS_ENABLED, WORKER_CONCURRENCY, PUBLISH_RETRIES, DONE_CACHE_SIZE), core/clients.py (S3), core/logger.py,
core/exception_handlers.py (без SQLAlchemy), common/exceptions.py, pytest.ini, requirements*.txt, raisa_config.yml,
setup/setup.sh + pip.conf (без npm-шага). Шаблонный core/nats_callback.py (consumer recognition.result — зона
ядра) удалён задачей 4.3: его место занял воркер core/worker.py.
Вырезано (Срез 0): web/ целиком и setup/.npmrc; из app.py — mount SPA (SPAStaticFiles, os.makedirs("web")),
CORS, регистрация SMB-сессии, импорт raisa_fastapi_protected_api, зависимость от SQLAlchemy в обработчиках ошибок;
db/, models/, repositories/, alembic/, alembic.ini; из настроек — OPENBAO.DATABASE, OPENBAO.SMB; из
requirements.txt — asyncpg, SQLAlchemy, alembic, smbprotocol, argon2-cffi, greenlet, Mako,
raisa_fastapi_protected_api; шаблонный README.md заменён описанием сервиса.
Добавлено (в скобках — задача openspec/changes/slice-0-skeleton/tasks.md):
api/src/parser/— интерфейсParserи заглушкаNullParser; разбор ОСВ §1 — Срез 2a (см. ниже), форм §12 — Срез 2b;api/src/contract/— pydantic-модели контракта v1.1 (запрос, результат, конверт);api/contracts/— копии схем (идентичность с../docs/contractsпроверяется вmake check) — 3.1;api/rules/— README о назначении; сами словари*.yaml(в документахrules/*.yamlотносительноapi/) появились в Срезе 2a — 4.1;api/src/core/nats.pyиapi/src/core/worker.py— подписка queue grouptabular-parserнаrecognition.tabular.request→ ack → S3uploads/→ парсер → S3results/→recognition.result— 4.2–4.3;api/pyproject.toml(ruff, mypy) иMakefile— 2.1;Dockerfile,api/.env.example,api/.env.test— 2.2.
Добавлено в Срезе 2a (парсер ОСВ; openspec/changes/slice-2a-osv-parser/)
Модули api/src/parser/ (конвейер сверху вниз; подробности и статусы — README.md, раздел «Парсер ОСВ»):
grid.py+readers/— модель листа и чтение xlsx/xls/xlsb; единственное место, где допустимы индексы и буквы колонок (column_letter— только диагностика).read_book(data, file_type) -> (листы, предупреждения), формат — по сигнатуре.rules.py— загрузчик словарейapi/rules/*.yaml;detect.py— вид листа и заголовок таблицы;header.py— шапка иgroup_schema;hierarchy.py— каскадoutline → indent → account_pattern → sums(+trivial);numbers.py—Decimalи ИНН;classify.py— строки и группировки;checks.py— контрольные суммы §1.4;osv.py— оркестраторOsvParser;deadline.py— кооперативный таймаут;errors.py— коды ошибок;cli.py— разбор файла.get_parser(settings)создаётOsvParserодин раз при старте (кэша нет);NullParser— только для тестов воркера.
Правила расширения:
- Новый формат ОСВ = фикстура в
../fixtures/osv/(или синтетикаs1x_…вtests/synthetic/generate.py) + синоним в YAML. Не новыйifпод файл, организацию, имя листа или номер строки; «нужна координата» означает, что алгоритм не по содержимому — остановиться и обсудить. - Нет координат: AST-линтер
tests/unit/parser/test_no_coordinates.pyпроверяетsrc/parser/**(кромеgrid.pyиreaders/): литералы букв колонок и адресов ячеек, имена листов по умолчанию, сравнения номера строки с числом, обращения к ячейке константами. Значения enum контракта вычисляются изsrc.contractи под линтер не попадают. - Каждая запись словаря должна срабатывать хотя бы на одном реальном или синтетическом файле
(
tests/fixtures/test_rules_coverage.py); мёртвый синоним удаляется, пробел в фикстурах закрывается синтетикой. - Golden-снимки (
../fixtures/golden/expected_osv_parse/, 88 файлов: 72 реальных + 16 синтетических) — регрессия, а не оракул; правятся толькоmake golden-updateс просмотром diff и отдельным коммитом вfixtures. Корректность проверяют property-тест и ручная сверка «Итого». - Деньги —
Decimal,floatв домене нет;needs_check— только по контрольным суммам, структуру под сходимость не подгонять.
Нетривиальное правило §1.4 (R-21): при сверке «сумма детей = родитель» обороты сравниваются по колонкам, а сальдо —
по колонкам либо по нетто дебет − кредит, если у родителя сальдо свёрнуто (1С показывает счёт нетто, а субсчета и
контрагентов — развёрнуто). Это решение зафиксировано в design №31; не ослаблять и не расширять без обсуждения.
Настройки: PARSE_MAX_BYTES (по умолчанию 50 МиБ; больше — UNSUPPORTED_FORMAT) и PARSE_TIMEOUT_SECONDS (120 с;
кооперативный, → TIMEOUT) — core/config.py. Фазу чтения таймаут не прерывает: до библиотеки чтения действуют
лимиты readers/_limits.py (распакованный zip ≤ 10 × PARSE_MAX_BYTES, площадь объединённых ячеек ≤ 200 000; R-23).
Контракт v1.1: rows[].extra (нераспознанные колонки таблицы по заголовку); CONTRACT_VERSION = "1.1"; копия схемы
в api/contracts/ идентична ../docs/contracts/ (проверяется в make check).
Тесты: make check ≈ 95 с, потому что make test (-m "not integration") включает тесты маркера fixtures (golden,
property, покрытие словарей); без каталога ../fixtures они пропускаются. Полный набор — make check-full (+ интеграция
с compose и сборка образа).
Проверки: make check (pytest, ruff check . && ruff format --check ., mypy src), make check-full перед PR.