85 Commits

Author SHA1 Message Date
Andrey Epifancev
0015532019 fix(parser): детектор заголовка — обязательны обороты и конец, ИНН без ведущего нуля (T4.1–T4.4, T4.6)
- T4.1 (R-12): строка групп обязана содержать «обороты» и «сальдо на конец»; строка
  только с «началом» и «концом» (преамбула) пропускается, нет подходящей — InvalidFormatError
  с недостающими группами лучшего кандидата.
- T4.2: сторона в заголовке в одну строку — после пунктуации («Сальдо на начало, Дт», «(Кт)»).
- T4.3: inn_from_cell — общий хелпер ИНН (10/12 цифр); число из 9/11 цифр дополняется
  ведущим нулём; переиспользуется классификатором (Task 8).
- T4.4: колонка ИНН по значениям — не меньше INN_MIN_VALUES = 3 значений-ИНН.
- T4.6: у слитого (в том числе только по вертикали) названия группы поиск подзаголовков
  ограничен его диапазоном.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 13:03:15 +04:00
Andrey Epifancev
a833945669 feat(parser): детекторы листов ОСВ, заголовка таблицы и колонок по содержимому
detect.py: classify_sheet (ОСВ / форма ББ-ОФР / прочее по словарю), find_table_header
(строка с ≥ 2 группами сумм, «Дебет/Кредит» под группой в пределах merged-диапазона
или до следующей группы, заголовок в 1–4 строки, в т.ч. «Нач. сальдо Дт» в одной
ячейке; без «начала»; свёрнутое сальдо в *_debit + collapsed; свёрнутые «Обороты» и
половинчатая пара Дт/Кт — InvalidFormatError с названием группы), text_col, inn_col
(заголовок «ИНН» или ≥ 80 % значений из 10/12 цифр), right_edge, extra_cols,
group_label_rows, first_data_row, is_header_repeat (две группы или копия строки блока).

Pattern.search_normalized — ячейка нормализуется один раз (T3.6). osv_groups: известные
группировки OTHER перечислены явно, чтобы строка-метка «Основные средства» в
неслитом заголовке была частью блока. Хелпер тестов gridbuilder (Sheet из «картинки»).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 12:54:11 +04:00
Andrey Epifancev
fcd609c633 feat(rules): словари ОСВ (заголовки, группировки, периоды, типы данных) и загрузчик
Словари api/rules/{osv_headers,osv_groups,periods,data_types}.yaml и src/parser/rules.py
(normalize_text, Pattern, load_rules, pydantic-валидация с RuntimeError по файлу и ключу,
currency_aliases по R-6). AST-линтер координат tests/unit/parser/test_no_coordinates.py
перенесён из Task 12 (R-7, правила R-4/R-5). Тест формулировок реальных ОСВ.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 12:28:46 +04:00
Andrey Epifancev
c6a768dc70 fix(parser): ячейки из одних пробелов не хранятся в Sheet.cells (T2.1)
make_cell возвращает None для строки, у которой strip() пуст (пробелы, NBSP и
прочие Unicode-пробелы): «пробелы = пусто» на уровне хранения, sheet.cell даёт
EMPTY_CELL. Тесты: пробельные ячейки xlsx/xlsb отсутствуют в cells, строка из
пробелов пуста, merged-диапазон с пробельным origin даёт "".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 12:16:28 +04:00
Andrey Epifancev
b5a3b480b2 feat(parser): модель листа Sheet и читатели xlsx/xls/xlsb
grid.py: Cell, MergedRange, Sheet (1-based, только непустые ячейки, ленивые
индексы строк и merged; диапазоны больше 10 000 ячеек проверяются перебором),
column_letter для диагностики. errors.py: ParserError и коды контракта.
readers: read_book определяет формат по сигнатуре (zip → XLSX/XLSB по
xl/workbook.bin, OLE2 → XLS), предупреждает о несовпадении с file_type;
ошибки библиотек → UnreadableError одной строкой. Числа — Decimal(str(value)),
bool отдельно, даты как datetime; xlsb без outline/merged с предупреждением.
Тесты: синтетическая книга openpyxl, подмена file_type, мусор/обрезанные
файлы, xlsb через подменённый pyxlsb; 66 реальных ОСВ читаются < 5 с с
outline и merged; xls «62 за 3 квартал 2024»: строка 15 — outline 3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 12:11:16 +04:00
Andrey Epifancev
38386b53a2 test: доступ к ../fixtures (ARM_KA_FIXTURES), параметризация по 66 ОСВ; зависимости openpyxl/xlrd/pyxlsb
- requirements: openpyxl==3.1.5, pyxlsb==1.0.10, xlrd==2.0.2, et_xmlfile==2.0.0 (транзитивная)
- tests/fixtures_helpers.py, conftest: фикстура fixtures_dir, параметризация osv_file/synthetic_file, skip маркера fixtures
- pyproject: mypy ignore_missing_imports для openpyxl/xlrd/pyxlsb, disallow_untyped_defs для src.parser.*

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 12:02:40 +04:00
Andrey Epifancev
777d4aa850 contract: needs_check-пример по docs (строка 3 CURRENCY, уровень 2), README v1.1
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 11:59:55 +04:00
Andrey Epifancev
947d2a6a51 contract: v1.1 — OsvRow.extra, CONTRACT_VERSION 1.1, копия схемы
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-05 11:57:22 +04:00
Andrey Epifancev
479a76f81b test: старт с минимальным окружением и страж «без БД и фронта» (M-8)
- test_app: приложение с Settings только из MinIO, адреса NATS и DEBUG
  стартует, /healthcheck отвечает 200 с uptime, version, nats.
- test_layout: в requirements.txt нет asyncpg, SQLAlchemy, alembic,
  smbprotocol, argon2-cffi, raisa_fastapi_protected_api; нет каталогов web/,
  api/alembic/, api/src/{db,models,repositories}/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 03:48:34 +04:00
Andrey Epifancev
8685441fcd fix: настройки — SERVICE_NAME только tabular-parser, PUBLISH_RETRIES ≥ 0 (M-1, M-3, M-4, M-5)
- SERVICE_NAME: Literal["tabular-parser"] (R-35); в config.py, .env.example,
  README и CLAUDE.md исправлено «имя queue group» — queue group задаёт
  константа REQUEST_QUEUE_GROUP в core/nats.py.
- PUBLISH_RETRIES: NonNegativeInt — 0 означает одну попытку без повторов,
  как и принимает NatsConnection.
- Удалён неиспользуемый модульный settings = get_settings() из config.py.
- pyproject.toml: убран override mypy для raisa_fastapi_protected_api
  (зависимость удалена D-1).
- tests/conftest.py: фабрика minimal_settings (Settings из временного .env с
  минимальным окружением, окружение процесса скрыто); test_config.py —
  умолчания при минимальном окружении (M-8), PUBLISH_RETRIES=0/-1,
  SERVICE_NAME≠tabular-parser.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 03:48:34 +04:00
Andrey Epifancev
bf2f60b3b5 fix: NATS — отказ в правах логируется отдельно; drain ограничен 10 с (I-3, M-7)
nats-py 2.14 при Permissions Violation не закрывает соединение и не роняет
subscribe/flush/publish, а только вызывает error_cb. Раньше это логировалось
как «Нет связи с NATS, повторяем подключение» с лимитом раз в минуту. Теперь
_on_error распознаёт «permissions violation» и пишет error
error_category=nats_permission с subject, каждый раз и без серии ошибок
подключения. Исправлены ложные утверждения docstring («ошибки прав
проявляются сразу»).

connect получает drain_timeout=DRAIN_TIMEOUT (10 с вместо 30 с nats-py):
STOP_TIMEOUT воркера + drain укладываются в 30 с платформы.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 03:46:33 +04:00
Andrey Epifancev
67a4311150 docs: докстринги контракта с точки зрения tabular-parser, паритет subjects с arm-core (T6.1-T6.3)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 03:29:28 +04:00
Andrey Epifancev
6491fdd538 test: проверка остановки воркера по снятым задачам и устойчивый teardown uploads (T10.1, T10.2)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 03:29:28 +04:00
Andrey Epifancev
a62d2e24b1 test: интеграционный round-trip сервиса против compose-NATS и MinIO (задача 4.4)
Приложение поднимается в процессе теста через lifespan с NATS_ENABLED=True; тест в роли ядра кладёт исходник в
uploads/, публикует запрос и собирает recognition.result по своему job_id: ack processing, затем error
NOT_IMPLEMENTED с правильными Nats-Msg-Id и валидные по схеме конверты; повтор запроса — только ещё один ack
(второго результата нет в окне тишины QUIET_WINDOW). Плюс сценарии spec: нет объекта в S3 -> FILE_READ_ERROR и
сервис работает дальше; schema_version 2.0 -> один SCHEMA_MISMATCH без ack.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 03:23:35 +04:00
Andrey Epifancev
b7d4f0c17a test, docs: порядок старта и проверка бакета в тестах приложения; README/CLAUDE.md без устаревших «добавляется задачей», spec без номера рулинга
T9.2: события старта worker.start → nats.start; _connect_s3 — info/warning/error по ответу проверки бакета, клиент
остаётся. T9.3: spec без «R-17». T9.4: README и CLAUDE.md описывают уже существующие файлы без отсылок к будущим задачам.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 03:18:09 +04:00
Andrey Epifancev
18276e607e fix: воркер — задание не теряется молча при остановке во время ack, невалидный дубль job_id не публикует SCHEMA_MISMATCH
I9.1: после ack повторная проверка приёма — warning error_category=shutdown, job_id снимается из «в работе».
T9.7: невалидное тело с job_id в работе/обработанным — warning и сброс (msg-id настоящего результата не занимается).
T9.5: error.message исхода парсера — одной строкой, ≤ 1000 символов. T9.6: docstring stop() про to_thread.
T9.1: тест конкуренции без опроса со sleep.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 03:17:11 +04:00
Andrey Epifancev
a7862c1843 feat: запуск воркера в lifespan при NATS_ENABLED и поле nats = ok | down | off в /healthcheck
Подписка — через on_connected NatsConnection; остановка: воркер (дорабатывает очередь) → NATS → S3. Клиент S3
остаётся и при неудачной проверке бакета, отсутствие бакета — warning (T2.1). Тест lifespan и healthcheck с фейками
(T8.7); формулировки nats в README, .env.example и spec приведены к R-17.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 03:09:06 +04:00
Andrey Epifancev
e1231dcdb4 feat: воркер распознавания src/core/worker.py — ack до чтения S3, дедуп job_id, таблица ошибок D-5
on_request: валидация (pydantic + MAJOR = 1) → повторный job_id получает только ack → ack processing → очередь;
рабочие корутины: S3 get → Parser через asyncio.to_thread → файл результата → конверт с metrics.duration_ms →
публикация с Nats-Msg-Id result:<job_id>:<attempt>. SCHEMA_MISMATCH без ack, FILE_READ_ERROR, UNKNOWN_ERROR,
NatsPublishError — error-лог. Docstring протокола Parser: ожидаемые сбои — исход error (T7.1).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 03:05:47 +04:00
Andrey Epifancev
6c8d7b6e9b fix: NATS — PUBLISH_RETRIES как повторы после первой попытки (R-16), flush после подписки, снятие подписок при сбое on_connected, проброс отмены в stop(), error-лог самопроизвольного закрытия
Находки ревью Task 8: T8.2–T8.6.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 02:57:23 +04:00
Andrey Epifancev
6525939888 test: интеграционные тесты NATS (заголовок Nats-Msg-Id, queue group) и S3Storage против compose; отметки Task 8 / 4.2
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 02:48:26 +04:00
Andrey Epifancev
b545143f9e refactor: lifespan подключается к NATS через core/nats.py (фоново, старт не ждёт NATS)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 02:47:47 +04:00
Andrey Epifancev
ff08905580 feat: слой NATS src/core/nats.py — фоновое подключение (nkeys/без), подписка на запросы в queue group, публикация с повторами
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 02:47:17 +04:00
Andrey Epifancev
4c32cd36ac feat: S3Storage.get_bytes/put_json и протокол Storage вместо шаблонных put_object/get_object
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 02:45:25 +04:00
Andrey Epifancev
84b5404215 feat: интерфейс парсера (Parser, ParseOutcome), NullParser с NOT_IMPLEMENTED, get_parser
Заглушка Среза 0 не читает входные байты; отметки Task 7 / 4.1.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 02:41:02 +04:00
Andrey Epifancev
b7af0d1824 docs: README каталога api/rules (словари синонимов парсера)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 02:41:02 +04:00
Andrey Epifancev
5b913803b4 feat: pydantic-модели контракта v1.0 (src/contract) и unit-тесты контракта
Запрос, конверт (processing|done|needs_check|error), файл результата, ОСВ и формы,
деньги Decimal <-> строка, subject'ы и Nats-Msg-Id. Тесты: идентичность с docs
(skip без docs, ARM_KA_DOCS), примеры, round-trip, согласованность моделей и схем.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 02:38:17 +04:00
Andrey Epifancev
f8a584af15 chore: копии схем контракта v1.0 и примеров в api/contracts
Скопировано из ../docs/contracts байт в байт (cp, diff -r чистый).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 02:38:17 +04:00
Andrey Epifancev
ebf87f39da chore: Dockerfile (python:3.11-slim), api/.env.example и api/.env.test
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 02:35:48 +04:00
Andrey Epifancev
8e15696913 chore: pyproject.toml (ruff, mypy), requirements-dev.txt, Makefile, маркеры pytest
Задача 2.1. make test допускает код 5 pytest (тестов пока нет). README: все цели Makefile создаются в 2.1 (T3.1).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 02:32:49 +04:00
Andrey Epifancev
a24fee09c6 style: форматирование ruff остальных файлов шаблона и тип уровня в InterceptHandler
Нужно для зелёного make lint (R-8).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 02:32:49 +04:00
Andrey Epifancev
1b87cd2fdf fix: бесконечное автопереподключение NATS и логирование разрывов
max_reconnect_attempts=-1 (D-6), колбэки disconnected/reconnected/closed,
options типизирован как dict[str, Any].

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 02:27:43 +04:00
Andrey Epifancev
9cdc62b26d chore: упрощены app.py, config.py и requirements.txt после выреза шаблона
Убраны SPA, CORS, SMB, РАИСА, SQLAlchemy-обработчики и зависимости БД/SMB;
настройки: ENV_FILE, SERVICE_NAME, NATS_ENABLED, WORKER_CONCURRENCY,
PUBLISH_RETRIES, DONE_CACHE_SIZE, MINIO_SECURE.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 02:25:35 +04:00
Andrey Epifancev
b428f68e9e chore: вырезан фронт и БД из шаблона
Удалены web/, setup/.npmrc, api/alembic/, api/alembic.ini, api/src/db/,
api/src/models/, api/src/repositories/; из setup/setup.sh убраны npm-шаги,
из .gitignore убрана строка Dockerfile. Задача 1.1 отмечена выполненной.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-04 02:22:43 +04:00
Andrey Epifancev
1bd97a3672 chore: вычищены остатки доменного кода, каркас проходит tsc/biome/rsbuild 2026-10-02 23:34:58 +04:00
Andrey Epifancev
29e5cc5bf4 chore: каркас из шаблона команды (DKB_ARMKA@ba172f8, доменный код удалён) 2026-10-02 23:15:38 +04:00