statement_money вместо to_money в разборе строк форм, предупреждения о неразобранном формате и об xlsb, сравнение условий формата с точностью Excel (15 значащих цифр), тесты и тексты. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
14 KiB
CLAUDE.md — tabular-parser
Общий контекст системы — ../CLAUDE.md и ../docs/. Этот файл — только про этот сервис.
Что это
Stateless-сервис разбора XLS/XLSX/XLSB: ОСВ по счетам (§1 правил) и ББ/ОФР из Excel (§12). Вход — NATS
files.tabular_uploaded (по умолчанию; задаётся OPENBAO__NATS__NATS_*_SUBJECT) + файл в S3 uploads/; выход — JSON по ../docs/contracts/recognition-result.schema.json
в S3 results/ + NATS files.tabular_parsed. Нет доступа к БД. Не знает про организации, периметры, план счетов —
возвращает структуру файла; нормализация и резолв — в arm-core.
Главное правило
Структура определяется динамически по содержимому (§1.0 правил): никаких координат ячеек, имён листов, букв колонок
в коде. Словари синонимов — rules/*.yaml. Фикстуры ../fixtures/osv/** — регрессия, не спецификация формата:
новый формат = новая фикстура + синоним, не новый if.
Тесты
- property-тесты инвариантов (§1.4) на всех файлах
../fixtures/osv/**и../fixtures/statements-xlsx/**; для пакета ДКБ../fixtures/osv-dkb/**—tests/fixtures/test_osv_dkb.py: ожидаемыеneeds_check— манифестtests/osv_dkb_expectations.yaml(данные), период — по имени каталога (2025-Q3,2024-10_2025-09); - снимки
../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 files.tabular_parsed — зона
ядра) удалён задачей 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наfiles.tabular_uploaded→ ack → S3uploads/→ парсер → S3results/→files.tabular_parsed— 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);inn_rows.py— строки ИНН рядом с контрагентом (OQ-163);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/, 123 файла: 72 реальных + 23 пакета ДКБ (osv-dkb/**до 1 МиБ, снимки вosv-dkb/) + 28 синтетических) — регрессия, а не оракул; правятся толькоmake golden-updateс просмотром diff и отдельным коммитом вfixtures. Корректность проверяют property-тест и ручная сверка «Итого». - Деньги —
Decimal,floatв домене нет;needs_check— только по контрольным суммам, структуру под сходимость не подгонять.
Нетривиальное правило §1.4 (R-21): при сверке «сумма детей = родитель» обороты сравниваются по колонкам, а сальдо —
по колонкам либо по нетто дебет − кредит, если у родителя сальдо свёрнуто (1С показывает счёт нетто, а субсчета и
контрагентов — развёрнуто). Это решение зафиксировано в design №31; не ослаблять и не расширять без обсуждения.
Счета без сальдо по аналитике (OQ-132, #11 п. 2): список счетов (91, 99) — данные api/rules/osv_checks.yaml, не код;
тождество строки на них не проверяется, «сумма детей = родитель» (обороты всегда, сальдо — только корень → субсчета) и
«Итого» сохраняются. Счёт 90 в список не входит. Не расширять список «чтобы файл прошёл» — только по решению владельца.
Настройки: 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.
Добавлено в Срезе 2b (парсер ББ/ОФР; openspec/changes/slice-2b-statement-parser/)
Модули: api/src/parser/book.py (BookParser: лимиты, чтение, дедлайн, вид листа, статус и карта ошибок; документы —
ОСВ в порядке листов, затем формы) и api/src/parser/statements/ (detect → columns → header → lines → checks →
merge/statement; подробности — README.md, раздел «Парсер форм (ББ/ОФР)»). get_parser(settings) теперь создаёт
BookParser (листы ОСВ по-прежнему разбирает osv.py).
Правила расширения форм:
- Новая форма или написание = строка в
api/rules/statement_*.yaml(+ синтетикаst0xвtests/synthetic/generate_statements.pyили реальный файл в../fixtures/statements-xlsx/). Неifпод файл, организацию или лист. Соотношения §7 — данныеstatement_checks.yaml. - Координат в
src/parser/**нет (кромеgrid.py,readers/): тот же AST-линтерtest_no_coordinates.py; слов формы в коде нет — только в YAML. Деньги —Decimal; единицы не нормализуются, первичка не мутируется. - Модули ОСВ (
header.py,classify.py,checks.py,hierarchy.py) под формы не правятся.
Тесты на реальных данных: манифест ожиданий — api/tests/statements_manifest.yaml (18 файлов). Golden — два каталога
в ../fixtures/golden/: expected_osv_parse и expected_statement_parse; правятся только make golden-update с
просмотром diff и отдельным коммитом в fixtures.
Контракт v1.2: CONTRACT_VERSION = "1.2"; documents[] получает BALANCE/INCOME/DISCLOSURE (statementDocument:
header, lines, checks); копия api/contracts/ идентична ../docs/contracts/. Значения ББ/ОФР из Excel — как их показывает
числовой формат ячейки (#42, statements/number_format.py; xlsb — как в файле + предупреждение); ОСВ формат не применяет.
Книга НПЗ в манифесте — done (итоги расходятся в пределах округления, OQ-138).