arm-ka-docs/context/05-domain-rules.md
Andrey Epifancev e6ba1e0e30 docs: §11 и OQ-77 — убрано допущение о сегментах таблицы (поправка человека 04.10.2026)
Разбиение одной формы на несколько таблиц в образцах не встречалось; наблюдается только несколько таблиц разных
форм в одном json (баланс + ОФР). Нештатный случай (повтор label) — NEEDS_CHECK, правило — Срез 3.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
2026-10-04 14:08:43 +04:00

44 KiB
Raw Blame History

05. Правила предметной области (выжимка из ТЗ, имена — v5.13)

Каждый раздел: источник в ТЗ → правило → где в модели. Где ТЗ молчит или противоречит — пометка [открыто] и ссылка на 07-open-questions.md.


§1. Парсинг ОСВ (tabular-parser + маппинг в ядре)

Источник: «Парсинг ОСВ» (DOC-260010), FNR01 (DOC-037740), реальные файлы fixtures/osv/.

1.0 Главный принцип: структура определяется динамически, не по координатам

ОСВ приходят из разных конфигураций 1С (8.2/8.3, УПП, ERP, БП 3.0), из других учётных систем и после ручной правки бухгалтером. Ни одна буква колонки, ни один номер строки, ни одно имя листа не являются контрактом. Фикстуры в fixtures/osv/ — это примеры двух организаций, а не спецификация формата. Парсер обязан:

  1. Найти лист(ы) с ОСВ по содержимому: любая ячейка, матчащая оборотно[- ]сальдовая ведомость (без учёта регистра, «ё/е», переносов строк). Один файл может содержать несколько листов с разными ОСВ → несколько document. Листы без признаков ОСВ/ББ/ОФР — пропускать с warning, не падать.
  2. Найти заголовок таблицы по содержимому: строка(и), где встречаются одновременно «Сальдо на начало…», «Оборот(ы) за период», «Сальдо на конец…» (с вариантами: «Нач. сальдо», «Обороты», «Кон. сальдо», «Дебет/Кредит», «Дт/Кт», «Д-т/К-т»). Заголовок может занимать 1–4 строки с merged-ячейками; группировки могут быть как под «Счет» в первой колонке, так и строкой «Субконто», так и перечислением через запятую (вариант 2).
  3. Определить колонки сумм по тексту, а не по позиции: для каждой из трёх групп («начало», «оборот», «конец») найти подзаголовки «Дебет»/«Кредит» в следующей строке под ней (с учётом merged-диапазонов). Допустимы ОСВ без «начала» (только обороты и конец), с одной свёрнутой колонкой сальдо (знак = сторона), с дополнительными колонками («Количество», «Валюта», «ИНН», «КПП», «Код»). Неизвестные колонки — сохранять в extra строки, не терять.
  4. Определить колонку текста строки — первая непустая слева от сумм (обычно A, но не обязательно); ИНН — колонка с заголовком ИНН или колонка, где ≥ 80 % непустых значений — 10/12 цифр.
  5. Определить иерархию строк по outline level Excel; если уровней нет (файл пересохранён, csv, другая система) — по отступу (ведущие пробелы / alignment.indent), по жирности счёта, по шаблону кода счёта и, в крайнем случае, по факту «сумма детей = родитель». Стратегии применяются каскадом, выбранная стратегия пишется в metrics.warnings.
  6. Шапку документа (организация, счёт, период, «выводимые данные») искать регулярками по всем ячейкам выше заголовка таблицы; отсутствие любого поля — не ошибка, а null + warning (ядро попросит аналитика уточнить при проверке).
  7. Самопроверка результата (контрольные суммы §1.4) — единственный способ понять, что структура угадана верно. Если тождества не сходятся — не «подгонять», а вернуть needs_check с диагностикой, какой шаг не уверен.
  8. Правила парсера — данные, а не код: словари синонимов заголовков, группировок, периодов лежат в tabular-parser/rules/*.yaml и расширяются без релиза. Новый формат ОСВ = новая фикстура + при необходимости новый синоним, а не новая ветка if.

Тесты парсера строятся по принципу «свойств»: для любого входного файла результат удовлетворяет инвариантам §1.4, число строк-контрагентов ≥ 0, Итого = корень. Фикстуры — регрессия, не оракул формата.

1.1 Шапка — два варианта 1С (как примеры, не исчерпывающий список)

Вариант 1 (все наши фикстуры):

r1  <Наименование организации>                       ← владелец ОСВ (сверить с legal_entity → document.counterparty_check)
r2  Оборотно-сальдовая ведомость по счету 60 за 1 квартал 2025 г.   ← счёт после «по счету», период после «за»
r4  Выводимые данные: БУ (данные бухгалтерского учета)              ← data_type шапки
r7  Параметры: | Валюта БУ, НУ: RUB   (или «руб.»)
r9  Счет          | Сальдо на начало периода | Обороты за период | Сальдо на конец периода
r10 Контрагенты   | [ИНН] | Дебет | Кредит | Дебет | Кредит | Дебет | Кредит
r11 Договоры                                                        ← может отсутствовать

Группировки — отдельные строки первой колонки блока заголовка (Счет, Подразделение, Контрагенты, Договоры), порядок строк = порядок вложенности. Ячейка ИНН в той же строке, что Контрагенты → has_inn = true.

Вариант 2 (встречается у других клиентов):

Оборотно-сальдовая ведомость по счету 60
Период: 1 квартал 2026 г.
Детализация по субсчетам, субконто: Контрагенты, Договоры     ← группировки через запятую, первая неявно ACCOUNT
Выводимые данные: сумма
Субконто | Сальдо на начало периода | Оборот за период | Сальдо на конец периода
         | Дебет | Кредит | Дебет | Кредит | Дебет | Кредит

Нормализация значений шапки в типы:

Текст в шапке group_type
Счет, Счета, Субконто (первая) ACCOUNT
Подразделение, Подразделения DEPARTMENT
Контрагент, Контрагенты COUNTERPARTY
Договор, Договоры, Договоры контрагентов CONTRACT
прочее (Валюта, Номенклатура, Статьи…) OTHER

Результат шапки → recognition_job.group_schema (по листу): {"locator":"sheet:Лист_1","account":"60", "period_text":"1 квартал 2025 г.","data_type":"BU","groups":[{"type":"ACCOUNT"},{"type":"COUNTERPARTY","has_inn":true}, {"type":"CONTRACT"}]} и → document(document_type=OSV, osv_account_code, period_year, period_quarter, is_cumulative, locator).

1.2 Колонки сумм

Заголовки сумм объединены (merged) и позиции колонок плавают между файлами даже одной организации. Определять по тексту (§1.0 п. 3). Всё правее последней распознанной колонки сумм — не часть таблицы (в фикстурах там встречаются посторонние числа) — складывать в extra, в суммы не брать. Числа могут быть numeric или текстом («1 234 567,89», «(1 234)», «-», «—», неразрывные пробелы) — нормализовать в Decimal; пустая ячейка/прочерк = NULL (не 0). Отрицательные возможны.

1.3 Строки

Для каждой строки табличной части: row_number (исходный номер), level (= outline level Excel; в xls — rowinfo_map), row_value (текст первой колонки), шесть сумм, ИНН (если колонка есть; оставить только цифры, иначе NULL), data_type (строки «USD», «Вал.», «Кол.» под контрагентом → CURRENCY/QUANTITY; по умолчанию — из шапки).

Классификация строки (п. 6–10 «Парсинг ОСВ»):

  1. Строка — счёт, если текст матчится ^\d{2,3}(\.\d{1,2}){0,2}(,\s.*)?$ (с опциональным названием после запятой: «60.01, Расчеты с поставщиками») → ACCOUNT, account_code (нормализованный), все дочерние строки наследуют счёт.
  2. Не-счёт: позиция в ветке = количество не-счётных предков + 1 → соответствующая группировка из group_schema (после последнего ACCOUNT). Увеличение уровня — ребёнок, равный — сосед, уменьшение — конец группы.
  3. После COUNTERPARTY дочерние продолжают классифицироваться по схеме (CONTRACT, затем OTHER).
  4. Строка «Итого» (level 0, последняя) — контрольная сумма всего документа; не сохранять как osv_entry [открыто №2], но использовать для сверки.
  5. Все остальные строки сохраняются (п. 11 «Парсинг ОСВ») с parent_entry_id [открыто №2].

Нормализация кода счёта: 60.1 → 60.01, 60.01.1 → 60.01.1, 76.7.1 → 76.07.1; account_root = первые две-три цифры (001 — забалансовый). account_id = точное совпадение с chart_of_account.code, иначе NULL (администратор добавит).

1.4 Контрольные суммы парсера

  • По каждой строке: opening_debit − opening_credit + turnover_debit − turnover_credit == closing_debit − closing_credit (±0.01).
  • По каждому родителю: суммы родителя = Σ детей того же data_type (по каждой из шести колонок). Строки CURRENCY/QUANTITY в суммирование не входят.
  • Строка счёта верхнего уровня == «Итого». Нарушение → recognition_job.status = needs_check, document.status = NEEDS_CHECK, причина — в metrics.warnings.

1.5 Маппинг периода (FNR01) → period_quarter, is_cumulative

Текст period_quarter is_cumulative
1 квартал, I кв., 3 месяца, январь-март, 01.01–31.03 Q1 true
2 квартал, II кв. Q2 false [открыто: 1С может считать нарастающим]
полугодие, 1 полугодие, 6 месяцев, январь-июнь Q2 true
3 квартал, III кв. Q3 false [открыто]
9 месяцев, январь-сентябрь Q3 true
4 квартал, IV кв. Q4 false [открыто]
год, за 2025 год, 12 месяцев, годовая Q4 true

Практика на фикстурах: ОСВ «за 3 квартал 2024» у обеих компаний — квартальные (opening ≠ сальдо на 01.01), а ОСВ «за 2024 г.» — годовые. Признак можно проверить по данным: если есть ОСВ Q1 того же года и Q3.opening == Q1.opening → нарастающая.

Сверка периода документа с периодом версии → document.period_check (MATCH / MISMATCH / UNKNOWN); загрузка не блокируется.


§2. Приведение периодов (DOC-259006)

Комплект (ББ + ОФР + ОСВ) за один период нарастающим итогом. Если ОСВ квартальные (is_cumulative = false), перед консолидацией строится расчётное представление (не хранится):

opening_* = opening_* первой ОСВ года (Q1)
turnover_* = Σ turnover_* по кварталам Q1..Qn
closing_* = closing_* последней ОСВ (Qn)

Ключ соединения строк между кварталами — (счёт, контрагент, [договор]). [открыто]: поведение при отсутствии промежуточного квартала; агрегировать на уровне контрагента или договора.


§3. Сводная ОСВ и сопоставление контрагентов (DOC-221278, FNR01 п. 9)

Вход: consolidation_version_id. Периметр — consolidation_member версии (не текущий related_group_member!).

  1. Для каждой компании периметра: document типа OSV со статусом CONFIRMED за year/quarter версии, входящие в consolidation_document версии (если версия ещё не зафиксировала документы — последние подтверждённые).
  2. Строки osv_entry этих документов с group_type = COUNTERPARTY; data_type IN (BU, SUM).
  3. Строки не агрегируются: сто строк по 60.01 у компании — сто строк в своде. counterparty_name/inn показываются как в файле.
  4. Резолв контрагента (при маппинге результата и повторно при построении свода, если counterparty_id IS NULL): ИНН → legal_entity.inn; иначе точное имя → legal_entity.name; иначе normalize(name) → legal_entity_alias.normalized_alias. Несколько кандидатов → NULL (ручное сопоставление). normalize = lower, trim, убрать пунктуацию/кавычки, убрать ОПФ (ООО, АО, ПАО, ИП…), схлопнуть пробелы.
  5. Признак контрагента: ГСК / ГК / Внешний (см. 04-data-model-guide.md).
  6. Количество строк свода = количеству отобранных osv_entry.

Ручное сопоставление (алгоритм в DOC-221278): список кандидатов = компании ГСК и ГК; поиск по ИНН/имени/алиасам; выбор → проверка дубля normalized_alias у другой компании (предложить перенести или отменить) → создать legal_entity_alias → osv_entry.counterparty_id = выбранное; counterparty_name/inn не трогать → audit_event(OSV_COUNTERPARTY_MAP) → автоприменить к другим строкам версии с тем же именем, если counterparty_id пуст, алиас однозначен и ИНН строки не противоречит.

Наблюдение по фикстурам: у «Счастливочной» контрагент «Русский бисквит» идёт с ИНН 3528052718 в колонке E, а у «Русского бисквита» ОСВ без колонки ИНН — контрагент «Пекарня Счастливочная ООО» только текстом → резолв по алиасу.


§4. Правила элиминации — пары счетов (Прил. 3 ТЗ, DOC-223044, FNR03)

Хранятся как данные: elimination_rule (+ elimination_rule_account), версионируются; версия фиксируется в consolidation_version.elimination_rules_version. Seed-набор для ББ (statement_type = balance, measure = closing):

№ Статья А (Дт) Счета А (closing_debit) Статья Б (Кт) Счета Б (closing_credit) mode Примечание
1 1150 ОС 03, 76.07.9 (01 — резервный) 1450 / 1550 Прочие обязательства 76.07.1 manual лизинг/аренда внутри группы; у Б: 76.07.1 − 76.07.5 − 76.07.9. Если у А нет ОСВ 03 — результат в строку «ППА» [открыто]
2 1170 ФВ долгосрочные 58.01 1310 / 1350 УК / добавочный 80, 83 manual гудвилл; если у Б нет ОСВ 80/83 — элиминируется 58.01 А; 1310(Б) − 58.01(А) > 0 → строка «Гудвилл»
3 1170 ФВ долгосрочные 58.03 1410 Займы долгосрочные 67.03 min
4 1240 ФВ краткосрочные 58.03 1510 Займы краткосрочные 66.03 min
5 1230 Дебиторка 76 (кроме 76.07.5, 76.07.9) 1510 Заёмные краткосрочные 66.04, 67.04 min проценты
6 1230 Дебиторка 60.02, 60.22, 60.32, 62.01, 62.03, 62.21, 62.31, 76 (кроме 76.07.5, 76.07.9) 1520 Кредиторка 60.01, 60.03, 60.21, 60.31, 62.02, 62.22, 62.32, 76 (кроме 76.07.1) min основная пара
7 1230 Дебиторка те же, что № 6 1410 Заёмные долгосрочные 67 min
8 1230 Дебиторка те же, что № 6 1510 Заёмные краткосрочные 66 min

Правило 5 % для пар: суммы равны → маппим; расхождение ≤ 5 % (к меньшей) → берём min; > 5 % → подсветка, авто не маппится, ждёт корректировки (ADJUSTMENT).

ОФР (statement_type = income, measure = turnover), из FNR03 — [открыто]: формализовать с методологами:

Статья Сторона А Сторона Б mode / НДС
2110 Выручка Кт 90.01 (с НДС, приоритет) либо Дт 62.01/62.21/62.31 по контрагенту Кт 60.01/60.21/60.31 + 76 (кроме 76.07., 76.03., плюс 76.07.2) full — берётся сторона А; без НДС = с НДС / (1 + средневзвешенная ставка/100)
2120 Себестоимость — — элиминация = ВГО по 2110 (зеркально, без НДС)
2310 Доходы от участия Дт 76.03 Кт 76.03 сторона А
2320 Проценты к получению Дт 76.03/76.05/76.08/76.09 Кт 66.02/.04/.06/.22/.24/.26, 67.02/.04/.06/.22/.24/.26 min
2330 Проценты к уплате Кт чётных субсчетов 66/67 + 76.07.5/76.08/76.28/76.38 Дт 76.03/.05/.08/.09 min

Дополнительные ручные корректировки ОФР (ТЗ 6.4, correction.reason_code): нереализованная выручка, акцизы (osv_entry.is_excisable для 90), агентское вознаграждение, внутригрупповая амортизация по аренде, проценты по внутригрупповым займам, доходы от участия, прочее.


§5. Автоматическое сопоставление ВГО (DOC-223044)

Границы: только closing_debit / closing_credit; только компании периметра версии; пары счетов — из §4; 1:1; строка участвует в одном сопоставлении целиком.

для каждой строки a компании A с counterparty_id = B ∈ периметр, group_type = COUNTERPARTY, data_type ∈ (BU, SUM):
  сторона a = closing_debit, если > 0, иначе closing_credit
  кандидаты = строки b компании B с counterparty_id = A, противоположная сторона,
              (счёт a, счёт b) входит в допустимую пару правила r (для каждого r — отдельный набор)
  1. точное 1:1 (сумма a == сумма b, одна пара счетов) и единственный вариант → матч, обе строки выбывают
  2. несколько точных по РАЗНЫМ правилам → не матчим, вниз таблицы на ручное
  3. нет точного: по каждому правилу — минимальное |a − b|; выбираем правило с минимальным расхождением;
     равное минимальное по разным правилам → не матчим (ручное)
результат пары: rule, A, B, сумма A, сумма B, deviation_amount = |A−B|, deviation_percent = dev / min(A,B) * 100,
                исходные osv_entry.id обеих сторон

Результат не хранится (DECISION-5) — возвращается как структура для шахматки, корректировок и КФО. Ручное решение по паре — correction с естественным ключом (rule_id, legal_entity_id, counterparty_id).

Открыто: агрегировать ли несколько строк одной компании по одному контрагенту и счёту перед матчингом (FNR03 говорит «несколько строк суммируются», DOC-223044 — «одна позиция = одна строка»). Рекомендация: агрегировать по (компания, контрагент, счёт, сторона), сохраняя список osv_entry.id как детализацию.


§6. Шахматка и результирующая таблица (DOC-221277)

Вход: версия + набор дебетовых счетов (kind ∈ A, AP) и кредитовых (kind ∈ P, AP); предустановки: «58.03 vs 67.03 + 66.03», «60 + 62 + 76 vs 60 + 62 + 76», «все счета».

Строки/столбцы — компании периметра; диагональ пуста.

amount_a(A,B) = Σ closing_debit  строк A с counterparty_id = B по дебетовым счетам
amount_b(A,B) = Σ closing_credit строк B с counterparty_id = A по кредитовым счетам
variance = |amount_a − amount_b|; variance_pct = variance / min(amount_a, amount_b) * 100; > 5 % — подсветка
ячейка: amount_a / amount_b / (variance)

Фильтры строк — как в §3 (COUNTERPARTY, BU/SUM, account_id IS NOT NULL).

Результирующая таблица «компания против группы» по статьям: Дебиторка (Дт 60.02, 60.22, 60.32, 62.01, 62.03, 62.21, 62.31, 76 кроме 76.07.5/76.07.9), Кредиторка (Кт 60.01, 60.03, 60.21, 60.31, 62.02, 62.22, 62.32, 76 кроме 76.07.1), ФВ (Дт 58.01, 58.03), Долгосрочные займы (Кт 67, 67.03), Краткосрочные (Кт 66, 66.03, 66.04); колонка «Итого по группе».


§7. Логические проверки ББ/ОФР (DOC-221281, FNR03 шаг 1)

Выполняются по каждой колонке-дате отдельно; ничего не меняют; результат не хранится.

ББ: 1700 − 1600 = 0; 1100 = 1110 + 1150 + 1160 + 1170 + 1180 + 1190 (+1120, 1130, 1140 если есть); 1200 = 1210 + 1220 + 1230 + 1240 + 1250 + 1260; 1300 = 1310 + 1320 + 1340 + 1350 + 1360 + 1370; 1400 = 1410 + 1420 + 1430 + 1450; 1500 = 1510 + 1520 + 1540 + 1550; 1600 = 1100 + 1200; 1700 = 1300 + 1400 + 1500. ОФР: 2100 = 2110 − 2120; 2200 = 2100 − 2210 − 2220; 2300 = 2200 + 2310 + 2320 − 2330 + 2340 − 2350; 2400 = 2300 − 2410 + 2420 (знаки 2410/2420/2430 — по ФСБУ 4/2023 уточнить [открыто]). Межформенная: 1370(отчётная дата) − 1370(начало года) = 2400(ОФР за период) — при расхождении КА может акцептовать с комментарием (financial_statement_meta.checks_accepted_*).

Когда: после распознавания; после «Сохранить» правок; при «Подтвердить данные». Непройдено → document.status = NEEDS_CHECK; пройдено/акцептовано + нажато «Подтвердить» → CONFIRMED + audit_event(DOCUMENT_CONFIRM). Отрицательные значения в формах — в скобках (2100) → -2100. Значения с confidence < 100 подсвечиваются.


§8. Корректировки, гудвилл, КФО, пороги

Корректировка (DOC-227278, ТЗ 6.8): запись на пару (правило, компания 1, компания 2) и форму (ББ/ОФР); debit_line_code / debit_adjustment, credit_line_code / credit_adjustment; распределение суммы одной стороны по нескольким строкам — дочерние записи (parent_correction_id, глубина 1). Контроль: Σ debit_adjustment ≈ Σ credit_adjustment (±5 %), иначе ВГО распределено не полностью. Комментарий обязателен. История — audit_event(CORRECTION_*), version_no++.

Гудвилл (DOC-259025, ТЗ 6.7): по каждой паре ГО→ДО периметра: FV = Σ closing_debit (58.01, контрагент ДО); UK = 1310 ББ ДО на отчётную дату (FNR03: Кт 80 + Кт 83, при отсутствии — 1310); share — ручной ввод; participation = (1310 [+ 1350?]) × share / 100; goodwill = FV − participation (может быть < 0); include_in_kfo + комментарий обязателен. Положительный итог — строка GOODWILL в разделе I КФО.

КФО (ТЗ 6.4, Прил. 10): по каждому коду строки ББ/ОФР: sum_amount = Σ по компаниям (значения отчётной колонки ББ / ОФР за период, в тыс. руб.) → eliminated_auto (по §4–5, пересчёт в тыс. руб.) → eliminated_manual = Σ correction APPLIED → goodwill_adjust → consolidated_amount; vgo_amount = eliminated_auto + eliminated_manual; разрез kfo_result_line_company (amount, share_percent = доля компании в консолидированном показателе). Эталон колонок — Консолидация_Группа РБ.xlsx, лист 01.10.2024: «значение на дату | в т. ч. взаимные обязательства | очищенные показатели | доля в показателе по группе».

Пороги (ТЗ 6.8, FNR03 шаги 4–5), kfo_result.threshold_*:

  • ББ: если 1300 < 0.1 × 1600 → Σ расхождений ≤ 1 % × 1600 (ВБ), иначе ≤ 10 % × 1300 (ЧА).
  • ОФР: если 2200 / 2110 < 10 % или отрицательна → ≤ 1 % × 2110 (выручка), иначе ≤ 10 % × 2200. Превышение → threshold_justification обязателен (принятие риска), подсветка.

Сравнение с КФО клиента (scenario = CONFIRM): client_kfo_line построчно: client_amount, client_vgo_amount (ручной ввод), bank_amount, bank_vgo_amount (из kfo_result_line), deviation_percent к наименьшему, причина.


§9. Статусные модели

9.1 КФО (consolidation_status, DOC-037734 + ТЗ 6.10)

Из В Кто Когда
— INITIATED КА РФ / КА ГО создана карточка, зафиксирован периметр, сценарий, подход
INITIATED DOCUMENTS_PROCESSING система/КА загрузка и распознавание
DOCUMENTS_PROCESSING DATA_CORRECTION КА есть документы NEEDS_CHECK
DATA_CORRECTION CONSOLIDATION_RF КА РФ все обязательные документы CONFIRMED; инициатор — РФ
DATA_CORRECTION CONSOLIDATION_GO КА ГО инициатор — ГО
CONSOLIDATION_RF ADJUSTMENT система расхождение ≥ 5 % в парах (подстатус)
CONSOLIDATION_RF VERIFICATION_RF КА РФ «отправить на верификацию»
VERIFICATION_RF CONSOLIDATION_GO РКА РФ подтвердил
VERIFICATION_RF CONSOLIDATION_RF РКА РФ вернул (комментарий обязателен)
CONSOLIDATION_GO CONSOLIDATION_RF КА ГО вернул на доработку (комментарий)
CONSOLIDATION_GO VERIFICATION_GO КА ГО если инициатор — КА ГО, верифицирует РКА ГО [открыто]
CONSOLIDATION_GO / VERIFICATION_GO VERIFIED КА ГО / РКА ГО внесены № и дата решения УОБ; версия read-only; пишется ex_snapshot
любой нефинальный INITIATED КА нужен другой периметр/сценарий/комплект
любой нефинальный CANCELLED КА аннулировано
VERIFIED — — только «новая версия» (previous_version_id)
из макета REJECTED, ARCHIVED ANL-035 — подтвердить с заказчиком

Каждый переход → consolidation_status_history + audit_event(CONSOLIDATION_STATUS_CHANGE).

9.2 Файл (file_status): UPLOADED → PROCESSING → PROCESSED | ERROR; любой → SUPERSEDED (замена файлом целиком,

superseded_by_file_id, version_no + 1). Физическое удаление после INITIATED запрещено (ANL-020) — только замена.

9.3 Документ (document_status): создаётся ядром при маппинге результата: все значения confidence = 100, проверки

пройдены, период и контрагент совпали → RECOGNIZED [открыто: нужен ли], иначе NEEDS_CHECK; «Подтвердить данные» → CONFIRMED; не разобран (нет шапки) → ERROR.

9.4 Задание (recognition_status): queued → published → processing → done | needs_check | error | timeout → dlq;

replay: dlq → queued (attempt + 1). max_attempts: OCR 1, парсер 3; reply_deadline OCR 5 мин.


§10. Роли и матрица прав (ТЗ 8.1, DOC-036960) — seed для role_permission

Роль (role.code) DIRECTORY DOCUMENT CONSOLIDATION KFO EX EXPORT USERS AUDIT
ADMIN VIEW, EDIT, DELETE, ADMIN (импорт) — — — VIEW — ADMIN VIEW
CURATOR_GO (Куратор КП ГО) VIEW, EDIT (ГК: создание, перезакрепление) — VIEW (по закреплённым ГК) VIEW VIEW — — —
KA_RF VIEW (свой РФ), EDIT (состав ГСК своей ГК) VIEW, EDIT (загрузка, правка, подтверждение) VIEW, EDIT (INITIATED…CONSOLIDATION_RF) VIEW, EXPORT VIEW EXPORT — —
RKA (РКА РФ) VIEW, EDIT (состав ГСК в рамках корректировки) VIEW VIEW, EDIT (VERIFICATION_RF) VIEW, APPROVE VIEW EXPORT — —
KA_GO VIEW, EDIT (состав ГСК, без ограничения по филиалу) VIEW, EDIT VIEW, EDIT (всё, что KA_RF + CONSOLIDATION_GO) VIEW, VERIFY (№/дата УОБ), EXPORT VIEW EXPORT — —
EXPERT VIEW VIEW VIEW VIEW VIEW — — —
CLIENT_MANAGER VIEW (свои клиенты) VIEW, EDIT (только загрузка по своей ГК) VIEW (статус) VIEW — — — —

Policy-слой (не права): KA_RF/RKA/CLIENT_MANAGER видят только ГК своего regional_office; редактирование версии — только в допустимом статусе и при отсутствии чужой активной consolidation_lock; ЗО (business_segment) — фильтр видимости. Матрица в ТЗ помечена «будет разработана» (сноска 29) — seed считать черновиком, редактируется администратором в UI.


§11. Маппинг JSON ИС Сканирование (FNR01/FNR02) → формы

Формат ответа — по реальным файлам fixtures/ocr/aep_3.json, aep_3_ofr.json, aep_6.json (проверено 04.10.2026; ТЗ и первая редакция этого параграфа расходятся с ними — см. OQ-13, OQ-14, OQ-77):

  • Каталог ответа <job_id>/ содержит несколько json — по одному на распознанный документ файла (пример: aep_3.json — успех, aep_3_ofr.json — ошибка по второму документу того же PDF). Шлюз читает все json каталога и склеивает в один ответ (raw/{job_id}.json: {job_id, files[{name, response, parse_error}]}); ошибка одного файла не отменяет остальные.
  • Корень файла: {file_uid, error {code, message}, data_ocr}. Успех — error.code == "" (поле error не null, а {"code": "", "message": ""}); ошибка — непустой code (INVALID_DOCUMENT, FILE_READ_ERROR, INVALID_FORMAT, RECOGNITION_FAILED, UNKNOWN_ERROR), data_ocr с пустыми полями и table_structure: [].
  • data_ocr — объект (не список): label, organization, inn, units, date — каждый {form_name, confidence} (в form_name шумный OCR-текст; date.form_name ненадёжна, ИНН может быть мусором), table_structure — список таблиц {label {form_name, confidence}, rows[]}: label.form_name «Бухгалтерский баланс» → BALANCE, «Отчет о финансовых результатах» → INCOME. Наблюдается: в одном json может быть несколько таблиц разных форм (в aep_3.json и aep_6.json — баланс и ОФР), каждая со своим label; тип документа задаёт label. Разбиение одной формы на несколько таблиц (сегменты) в образцах не встречалось и не предполагается; если одна форма вдруг придёт двумя таблицами или label повторится — это нештатный случай: документ не создаётся молча, а уходит в NEEDS_CHECK с причиной (решение — Срез 3, OQ-77). Неизвестный label → OTHER.
  • rows[]: row_index, cells[] {column_index, value, confidence}; column_index не обязательно подряд (бывают 0, 1, 2, 4, 5), value — сырой OCR-текст (мусор в ячейках, переносы строк, скобки для отрицательных, пробелы-разделители тысяч). В aep_6.json rows[0] — строка-шапка («Наименование показателя», «Код», «На 30 июня 2025 г», …); в aep_3.json шапки нет — первая строка данных. Бывают строки без кода (расшифровки «в том числе…», OQ-36) и код-строка внутри ячейки наименования («АКТИВ / I. Внеоборотные активы / Гудвил» с кодом 1105).
  • confidence — числа 0–100 (в ТЗ — доли 0..1; в реальных файлах проценты) → numeric(5,2) как есть.

Маппинг (Срез 3): таблица table_structure[i] с label «Бухгалтерский баланс» / «Отчет о финансовых результатах» → document (BALANCE/INCOME, locator = pages:…) + financial_statement_meta (ИНН, имя, units, report_date, okud — из organization/inn/units/date.form_name, с низкой уверенностью → NEEDS_CHECK) + financial_statement_column (из шапки rows[0], если она есть; period_date распарсить из «На 30 июня 2025 г», is_current = первая; без шапки — колонки без дат, документ NEEDS_CHECK) + financial_statement_line (cells[1] код, cells[0] имя; только строки с кодом) + financial_statement_value (число: убрать пробелы/-, скобки → минус; confidence; is_recognized). Сверки: ИНН/имя vs legal_entity → document.counterparty_check; дата vs период версии → period_check.


§12. ББ и ОФР из Excel (tabular-parser, второй тип документа)

Источник: ТЗ 6.2 («парсинг ОСВ, бухгалтерского баланса и ОФР (эксель файлов)»), DOC-037734, DOC-221281; формат — Приказ 66н / ФСБУ 4/2023, но на практике это и выгрузка из 1С («Регламентированный отчёт»), и шаблон банка (Копия Рекомендуемые формы расшифровок…, лист Отчетность в Анализ *.xlsx), и вручную набитая таблица.

Принцип тот же, что в §1.0: структура ищется по содержимому, результат — тот же statementDocument контракта, что и у scan-gateway, чтобы ядро маппило ББ/ОФР одинаково независимо от источника (confidence = 100, is_recognized = true для табличного пути).

  1. Классификация листа: ячейки с «Бухгалтерский баланс» / «0710001» → BALANCE; «Отчет о финансовых результатах» / «Отчет о прибылях и убытках» / «0710002» → INCOME. На одном листе могут быть обе формы подряд (ББ, затем ОФР) — разрезать по второму заголовку; в одном файле несколько листов — несколько document. Листы ОСВ и форм в одном файле — допустимо.
  2. Колонка кодов строк: та, где ≥ 60 % непустых значений — четырёхзначные коды из допустимого набора (1110–1700 для ББ, 2110–2500 для ОФР, плюс 5xxx для расшифровок). Заголовок «Код» необязателен. Если колонки кодов нет — попытаться сопоставить по наименованиям показателей (словарь rules/statement_lines.yaml: «Основные средства» → 1150) с пометкой line_code_confidence < 100 и warning; агент-аналитик подтвердит.
  3. Колонка наименований: ближайшая текстовая слева от кодов; «Пояснения» (1–2 символа/цифры) слева от неё — не наименование.
  4. Колонки-периоды: ячейки заголовка справа от кодов, матчащие дату («На 31 декабря 2024 г.», «31.12.2024», «2024», «За 9 месяцев 2025 г.», «За январь–сентябрь 2025 г.») → financial_statement_column с period_date (для ОФР — дата конца периода) и is_current = самая поздняя. Число колонок — от 1 до 3+. Колонки без даты («Изменение», «%») — в extra.
  5. Значения: Decimal по правилам §1.2; скобки → минус; «–»/«-»/пусто → NULL. Единица измерения — из шапки («тыс. руб.», «млн руб.», ОКЕИ 384/385) → header.units; если не найдена — по порядку величины относительно ОСВ той же организации не угадывать, оставить NULL (ядро спросит).
  6. Шапка: организация, ИНН, дата, ОКПО/ОКОПФ/ОКФС/ОКВЭД — регулярками по области выше таблицы; как в §1.0 п. 6.
  7. Самопроверка: формулы §7 по каждой колонке; расхождение > 0 (в единицах формы) → needs_check с перечнем непройденных соотношений в metrics.warnings. Это не замена проверок ядра, а сигнал, что структура угадана неверно (например, перепутаны колонки периодов).
  8. Расшифровки к строкам (лист К балансу, рекомендуемые формы банка) — в первой очереди не разбирать; классифицировать как DISCLOSURE/OTHER и сохранить locator.

Что это даёт ядру: один маппинг statementDocument → document + financial_statement_* для PDF и Excel; экран «Проверка распознавания» для Excel-источника показывает слева не страницы, а рендер листа (или просто таблицу) — UI-решение вне парсера.