Andrey Epifancev dd454983e6 openspec: спека Среза 3b — формы ББ/ОФР в ядре и API до сводной ОСВ (#36)
proposal, design (D-1…D-14, решения без обсуждения №1–10), дельты osv-result-mapping (маппинг форм, статус),
statement-checks (новая: §7, межформенная, акцепт), documents-api (форма, правка, акцепт, подтверждение, комментарии,
исходный файл), consolidations-api (CSV чек-листа), directories-api (documents_count), users-admin (справочник имён).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-07 08:21:57 +04:00

10 KiB
Raw Blame History

Spec Delta: osv-result-mapping

MODIFIED Requirements

Requirement: Цикл маппинга отделён от inbox

Фоновая задача ядра SHALL с периодом MAPPING_POLL_SECONDS (5) выбирать одно задание со status ∈ {done, needs_check}, чей файл имеет status = PROCESSING и current_recognition_job_id = это задание (FOR UPDATE SKIP LOCKED), проверять, что result_s3_object относится к бакету хранилища и его ключ начинается с results/{file_id}/{job_id}/ (OQ-70), читать объект из S3 вне транзакции, валидировать RecognitionResult v1.x и в одной транзакции создать документы и строки (листы ОСВ и формы ББ/ОФР), записать recognition_job.group_schema, перевести файл в PROCESSED и выполнить автозамену. Результат без документов SHALL давать неповторяемую ошибку маппинга «в результате нет документов»; результат с формами, но без ОСВ, ошибкой MUST NOT быть. Inbox MUST NOT читать файл результата и MUST NOT создавать документы.

Scenario: Результат смапплен

  • WHEN в S3 лежит результат парсера для rb/2024-FY/ОСВ_66 за 2024.xls, задание done, файл PROCESSING
  • THEN не позднее чем через MAPPING_POLL_SECONDS + время маппинга файл — PROCESSED, создан один document (OSV, osv_account_code = "66", period_year = 2024, period_quarter = Q4, is_cumulative = true, locator = sheet:Лист_1), osv_entry — все строки результата кроме TOTAL, group_schema задания содержит [{"locator": "sheet:Лист_1", "groups": [...]}]

Scenario: Ключ результата вне папки задания

  • WHEN result_s3_object.key = results/<другой file_id>/.../result.json
  • THEN файл — ERROR с ошибкой маппинга, документы не созданы, повтор не выполняется

Scenario: Сбой маппинга с повтором

  • WHEN вставка строк падает по ошибке БД
  • THEN транзакция откачена, metrics.mapping_attempts = 1, файл остаётся PROCESSING; на третьей неудаче — ERROR с metrics.mapping_error

Scenario: Повторный маппинг идемпотентен

  • WHEN маппинг прерван после частичной вставки (имитация) и выполнен снова
  • THEN у файла ровно один набор документов и строк, дублей по (document_id, row_number) нет

Scenario: Файл только с формами

  • WHEN смаплен golden-снимок mezheninovskaya/2025-Q3/1_Форма1_бух_09 (одна форма BALANCE, ОСВ нет)
  • THEN файл PROCESSED, создан документ BALANCE, ошибки «нет документов ОСВ» нет

Scenario: Пустой результат

  • WHEN результат валиден по схеме, но documents = []
  • THEN файл ERROR с metrics.mapping_error = "в результате нет документов", повтор не выполняется

ADDED Requirements

Requirement: Документ формы ББ/ОФР

Каждый statementDocument с document_type ∈ {BALANCE, INCOME} SHALL давать document (osv_account_code = NULL, is_cumulative = NULL, locator из результата, period_check = UNKNOWN) и financial_statement_meta 1:1. Дата периода — header.report_date, при её отсутствии — самая поздняя columns[].period_date; period_year — год даты, period_quarter — квартал, если дата — последний день марта, июня, сентября или декабря, иначе NULL с предупреждением; reporting_date — эта дата. Шапка: inn, inn_original_value = header.inn, inn_confidence, inn_verified = (inn_confidence = 100); organization_name; report_date = header.report_date, report_date_original_value = header.report_date_text; units = units_original_value = header.units; okud, okpo, okopf, okfs, address — как пришли; statement_type — balance/income. Колонки — по columns[], is_current = true у колонки с самой поздней period_date. Строки — по lines[] с исходниками кода и названия; повтор кода — первая строка и предупреждение. Значение SHALL создаваться на каждую пару «строка × колонка»: из результата — amount, string_value, confidence, is_recognized; отсутствующая ячейка — все поля NULL; is_modified = false, original_amount = NULL. checks результата MUST NOT использоваться для статуса. counterparty_check — тем же правилом, что у листа ОСВ (ИНН шапки, иначе имя или алиас владельца).

Scenario: Форма из Excel

  • WHEN смаплен golden-снимок mezheninovskaya/2025-Q3/1_Форма1_бух_09
  • THEN документ BALANCE, period_year = 2025, period_quarter = Q3, reporting_date = 2025-09-30; у financial_statement_meta 37 строк и по значению на каждую пару строка × колонка

Scenario: Книга с двумя формами

  • WHEN смаплен golden-снимок книги НПЗ npz/2025-Q3/…НОВОРОССИЙСКИЙ ПРОКАТНЫЙ ЗАВОД
  • THEN у файла документы BALANCE (132 строки) и INCOME (43 строки), оба за 2025 Q3

Scenario: Форма из OCR без даты отчёта

  • WHEN смаплен эталон нормализатора ocr/expected_normalize/aep_6 (дата отчёта null, колонки баланса 2025-06-30, 2024-12-31, 2023-12-31)
  • THEN документ BALANCE за 2025 Q2, is_current — у колонки 2025-06-30, report_date в шапке NULL, inn NULL

Scenario: Дата не конец квартала

  • WHEN дата отчёта формы — 2025-10-31
  • THEN period_year = 2025, period_quarter = NULL, предупреждение маппинга, документ NEEDS_CHECK

Requirement: Повтор формы в файле и прочие документы

Второй документ BALANCE или INCOME в одном файле MUST NOT создаваться: предупреждение маппинга, первый документ — NEEDS_CHECK. Документы DISCLOSURE и OTHER результата SHALL давать по одному document каждого типа на файл без данных (locator — все locator этого типа через запятую, статус RECOGNIZED). Форма BALANCE/INCOME без строк SHALL создаваться с шапкой и колонками.

Scenario: Расшифровки

  • WHEN смаплен синтетический снимок st06_header_variants (форма INCOME и пять DISCLOSURE)
  • THEN у файла два документа: INCOME и один DISCLOSURE с пятью locator через запятую

Scenario: Форма без колонки кодов

  • WHEN смаплен синтетический снимок st03_no_codes (BALANCE, строк 0)
  • THEN документ BALANCE NEEDS_CHECK с шапкой и колонками, строк нет

Requirement: Статус документа формы

Документ BALANCE/INCOME SHALL быть RECOGNIZED, только если одновременно: задание done; нет непройденных неакцептованных жёстких проверок §7 (statement-checks); counterparty_check ≠ MISMATCH; period_year и period_quarter определены; у каждого значения is_modified = true либо (is_recognized не false и confidence равна 100 или NULL); нет предупреждений маппинга по документу; есть хотя бы одна строка. Иначе NEEDS_CHECK. Статус SHALL пересчитываться при маппинге, после правки формы и после акцепта, кроме документа CONFIRMED. Документ формы NEEDS_CHECK SHALL участвовать в переходе версии в DATA_CORRECTION наравне с ОСВ (требование «Документ NEEDS_CHECK переводит версию в корректировку»).

Scenario: Чистая форма Excel

  • WHEN смаплен mezheninovskaya/2025-Q3/1_Форма2_бух_09 (ОФР, done, соотношения сходятся, ИНН шапки = ИНН владельца файла)
  • THEN документ INCOME RECOGNIZED

Scenario: Форма OCR

  • WHEN смаплен эталон ocr/expected_normalize/aep_3 (уверенность значений < 100, ИНН шапки ≠ ИНН владельца)
  • THEN документ BALANCE NEEDS_CHECK, counterparty_check = MISMATCH

Scenario: Форма двигает версию

  • WHEN версия ГК 2025 Q3 в DOCUMENTS_PROCESSING, в периметре ЮЛ файла; смаплена форма BALANCE за 2025 Q3 в NEEDS_CHECK
  • THEN версия в DATA_CORRECTION, привязки consolidation_document нет