- 07-open-questions: OQ-149 переписан (пустой код у «БАЛАНС»/«Выручка», предупреждения №29, `|` экранирован), новый OQ-152 (повтор кода 2410 в aep_6), уточнены OQ-137 и OQ-144 (Input) - 05-domain-rules: §11 locator `file:<имя json>;table:<i>`; §11.1 п. 2 (длина даты №31), п. 4 (предупреждения о пропущенных строках), п. 7 (все точки удаляются, Money и confidence №30), п. 8 (ИНН неуверенно), п. 9 (№28), п. 10 (редакция не определена) - 03-architecture: формулировка про `.part`; 06-test-data, dev/README (сценарии 2/2а), repo-templates/scan-gateway.CLAUDE.md — раскладка `<job_id>.<ext>` и пары по sha256 - dev/compose, qa-guide §6.8/§8 (ожидаемые предупреждения по эталонам, OQ-151), 09-delivery-plan (статус 3a) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
122 KiB
122 KiB
07. Открытые вопросы (реестр)
Правило: агент не закрывает вопросы сам. Если решение нужно для текущего среза — делает явное допущение, помечает его
в коде # ASSUMPTION(OQ-nn) и в OpenSpec-change, и переносит вопрос в статус «допущение принято». Пользователь закрывает
вопросы с аналитиками/методологами и обновляет этот файл.
| ID | Вопрос | Откуда | Влияет на | Статус / допущение |
|---|---|---|---|---|
| OQ-01 | Хранить ли все строки ОСВ или только контрагентские; нужен ли parent_entry_id (замечание 2 ревью v5.13) |
DBML, «Парсинг ОСВ» п. 11 | срез 2 (парсер, osv_entry) |
закрыт решением владельца 05.10.2026: хранить все строки ОСВ с level и parent_entry_id; строку «Итого» не хранить, использовать только для сверки (парсер отдаёт её как group_type = TOTAL, маппер Среза 2c отбрасывает) |
| OQ-02 | ОСВ «за 2/3/4 квартал» — квартальная или нарастающая? Как определять автоматически | FNR01 маппинг периода | срез 2, §2 правил | закрыт решением владельца 05.10.2026: ОСВ «за N квартал» — квартальная; признак сверяется по данным: если есть ОСВ Q1 того же года и opening совпадает — нарастающая (парсер ставит is_cumulative по тексту, ядро переопределяет при маппинге/в консолидации); допущение парсера (Срез 2a): period_quarter и is_cumulative вычисляются только по тексту периода (rules/periods.yaml; Q2–Q4 «N квартал» → false), сверка по данным — в ядре, подсрез 2c (ASSUMPTION(OQ-02)) |
| OQ-03 | Приведение квартальных ОСВ к нарастающим: ключ соединения (контрагент vs договор), пропуск промежуточного квартала | DOC-259006 | срез 4 | закрыт решением владельца 06.10.2026 (Срез 4): ключ склейки — (счёт, контрагент по сырому имени и ИНН), договоры внутри контрагента (строка COUNTERPARTY уже содержит их сумму); пропуск квартала — пробел по паре (ЮЛ, счёт), а не блокирующая ошибка: строк пары в своде нет, пробел отдаётся в gaps[] сводки и в листе «Пробелы» экспорта; квартал без строки ключа = нули; дубль ключа внутри квартала суммируется. Правило — 05-domain-rules.md §2; код — domain/consolidation_periods.select_sources, domain/consolidated_osv.build |
| OQ-04 | ОСВ по субсчёту (58.03 вместо 58) закрывает ли обязательный документ «ОСВ 58» | фикстуры | чек-лист | закрыт решением владельца 05.10.2026: нет, ОСВ по субсчёту (58.03) — только субсчёт, обязательный документ «ОСВ 58» закрывает только полная ОСВ по счёту 58; чек-лист считает документ выполненным при osv_account_code = код правила |
| OQ-05 | Нужен ли статус document.RECOGNIZED или все идут через NEEDS_CHECK → CONFIRMED |
DBML | срез 2–3 | закрыт решением владельца 05.10.2026: статус RECOGNIZED нужен: документ после успешного распознавания — RECOGNIZED, при нарушениях — NEEDS_CHECK, после подтверждения аналитиком — CONFIRMED |
| OQ-06 | Замена одного документа внутри многодокументного файла | DBML | срез 3 | решено: замена файлом целиком |
| OQ-07 | Матчинг ВГО: одна строка = одна позиция (DOC-223044) или агрегировать по (компания, контрагент, счёт) (FNR03)? | DOC-223044 vs FNR03 | срез 5 | допущение: агрегировать по (компания, контрагент, счёт, сторона) с детализацией по osv_entry.id |
| OQ-08 | Знак порога 5 % в правиле пары (≤ или <), и к чему считать % — к меньшей сумме (Методика сноска 14) | FNR03 (знак потерян) | срез 5 | допущение: ≤ 5 % к меньшей |
| OQ-09 | Правила элиминации ОФР (2110/2120/2310/2320/2330): точный состав субсчетов, источник средневзвешенной ставки НДС, выбор субсчетов «работником ПА самостоятельно» | FNR03 | срез 6 | не реализовывать авто-элиминацию ОФР до решения; только ручные correction |
| OQ-10 | Гудвилл: входит ли стр. 1350 (добавочный капитал) в базу участия; УК из ББ (1310) или из ОСВ 80/83 | DBML, DOC-259025, FNR03 | срез 6 | допущение: 1310 из ББ; 1350 — настраиваемый флаг, по умолчанию выключен |
| OQ-11 | Строка «ППА» в КФО (правило № 1, когда у А нет ОСВ 03) — что это и куда | FNR03 | срез 6 | не реализовывать |
| OQ-12 | Формулы ОФР: знаки 2410/2420/2430 по ФСБУ 4/2023 vs 66н; состав 1100 (1120–1140) | DOC-221281 | срез 3 | допущение: формулы §7, строки без значения = 0; расширение 07.10.2026: формулы и справочники по редакциям форм (66n / fsbu4) — OQ-146 |
| OQ-13 | confidence от ИС Сканирование — доли 0..1 или проценты 0..100; имя поля организации (full_name vs form_name) |
FNR02 vs aep_*.json |
срез 3 (scan-gateway) | закрыт для scan-gateway (04.10.2026, по aep_3.json, aep_6.json): confidence — числа 0–100 (не доли); название организации — data_ocr.organization.form_name (шумный OCR-текст), ИНН — inn.form_name, единицы — units.form_name, дата — date.form_name (в aep_6.json мусор); full_name/inn.inn/units.measurement из ТЗ в реальных файлах нет. Нормализация — Срез 3 |
| OQ-14 | reporting_date в JSON ИС Сканирование — «ждём обновлённый JSON» |
FNR01 | срез 3 | уточнено (04.10.2026): отдельного поля reporting_date в ответе нет; date.form_name ненадёжен (в aep_6.json — «Общество с ограниченной ответственностью», в aep_3.json — «2|0|2|4|»); даты колонок — в строке-шапке rows[0] таблицы («На 30 июня 2025 г» в aep_6.json), в aep_3.json шапки нет. Допущение прежнее: парсить из шапки таблицы, при её отсутствии — NEEDS_CHECK; решение — Срез 3 |
| OQ-15 | Таймаут OCR 5 мин / 0 повторов (ТЗ 3) vs outbox с повторами (DBML) | DOC-037736 | срез 3 | решено DBML: max_attempts OCR = 1, парсер = 3 |
| OQ-16 | Матрица прав (ТЗ сноска 29 «будет разработана»); роли макета (Аналитик ДКБ, Наблюдатель) ≠ ТЗ (ANL-034) | ТЗ 8.1, макет | срез 1 | допущение: seed по §10 правил |
| OQ-17 | user_role M:N или одна роль на пользователя |
DBML | срез 1 | закрыт реализацией Среза 1 (05.10.2026): M:N — user_role, PUT /admin/users/{id}/roles заменяет набор ролей |
| OQ-18 | Статусы REJECTED / ARCHIVED из макета (ANL-035); VERIFICATION_GO (РКА ГО) — есть ли в маршруте |
DBML, DOC-037734 | срез 7 | допущение: REJECTED не вводить (возврат = переход + комментарий); ARCHIVED — флаг фильтра; VERIFICATION_GO — есть |
| OQ-19 | consolidation_lock (взять в работу / перехват, ANL-036) — подтвердить с заказчиком |
макет | срез 7 | закрыт решением владельца 06.10.2026 (Срез 4): блокировка «взять в работу» закрывает все правки версии (периметр, статус, ручное сопоставление; загрузка и подтверждение документов блокировкой не ограничиваются); перехват — любому с CONSOLIDATION_EDIT, с обязательной причиной (takeover_reason), событие CONSOLIDATION_LOCK_TAKEOVER; истечения нет (OQ-119). 05-domain-rules.md §10 |
| OQ-20 | company_group.crm_id обязателен (модалка) или nullable (UC-02, DBML) |
DOC-221280 | срез 1 | решено DBML: nullable + code |
| OQ-21 | «Дата перевода» ЮЛ в другую ГК — помечена «??» | DOC-221280 | срез 1 | закрыт решением владельца 05.10.2026: дата перевода — effective_date ≥ сегодня, пишется в legal_entity_history; перевод применяется сразу; граница суток — по МСК (OQ-96, follow-up arm-core#8) |
| OQ-22 | Ретеншн results/ в S3 (ARCH-010) |
DBML | срез 2 | допущение: как uploads/ |
| OQ-23 | Расшифровки (DISCLOSURE): разбирать или только хранить (ANL-040) |
DBML | — | только хранить |
| OQ-24 | Форма 276-ОД — состав и шаблон | FNR03 3.4 | — | вне scope |
| OQ-25 | Состав ГСК для тестовой группы «Русский бисквит»: входит ли ОАО «ЧКФ», ООО «ОФГ», «Лидер» | фикстуры | тесты консолидации | частично закрыт по выгрузке ЕЦП (04.10.2026): ГК «Русский Бисквит» (1-AZFL6BQ, Санкт-Петербургский РФ) — 21 запись; флаг «входит в ГСК» только у АО «Русский бисквит» и ООО «Пекарня Счастливочная»; ЧКФ, ОФГ, Малечкинская птицефабрика, ТК РБ, Регион Финанс и др. — в ГК, но не в ГСК (срез — fixtures/directories/ecp-export-russkiy-biskvit.xlsx). Периметр тестовой консолидации — две компании; OQ-25 остаётся про то, верен ли состав ГСК в CRM |
| OQ-26 | Единицы: ББ/ОФР в тыс. руб., ОСВ в руб. — правило округления при элиминации (до тыс., банковское?) | — | срез 6 | допущение: считать в рублях, округлять до тыс. руб. на выходе в kfo_result_line (ROUND_HALF_UP) |
| OQ-27 | Поля ТЗ 5.1 (сценарий, подход, гудвилл) не в модалке создания консолидации | макет [42] vs ТЗ 5.1 | срез 4, 6 | решено (владелец макета, 03.10): approach = BASIC всегда в первой очереди, EXTENDED позже — определять по набору загруженных документов; goodwill_enabled — переключатель на вкладке «Гудвилл»; scenario = CONFIRM включается через меню «…» консолидации в списке |
| OQ-28 | 41) |
макет [41] | срез 3 | закрыт: реализовать по макету; для ОСВ (xlsx) левая панель — уточнить |
| OQ-29 | Макет: нет экранов пользователей / ролей / матрицы прав (ТЗ 8.1, role_permission); в журнале макета роли «Аналитик ДКБ», «Кредитный аналитик», «Утверждающий РФ», «Верификатор ГО», «Система распознавания / Сервис» (ANL-034, снимок 43-02) |
макет vs ТЗ 8.1 | срез 1 | допущение: минимальный экран администратора по §10 правил; роли — коды ТЗ 8.1, названия макета не используются |
| OQ-30 | Макет: шахматка и КФО показаны в тыс. руб. с 2 знаками, ОСВ — в рублях; где и как округлять (связано с OQ-26) | макет [30] [31-01] | срез 5–6 | допущение: хранить и считать в рублях, переключатель единиц — только отображение |
| OQ-31 | КПП ЮЛ: в макете колонка и поле форм (50, 61, 51-02, 79), в DBML v5.13 у legal_entity поля нет. Нужен ли КПП (и в импорте, и для ИП, у которых его нет)? |
макет [50] [51-02] | срез 1, DBML v5.14 | закрыт решением владельца 04.10.2026: КПП не нужен (DBML v5.14 без поля; импорт его не читает; формы — без КПП) |
| OQ-32 | ЮЛ в макете состоит одновременно в ГСК и ГСЗ («Входит в ГСК» и «Входит в ГСЗ» заполнены, [79]); ТЗ 6.1 / ANL-030 — только в одной. Что верно? | макет [79] [51-02] | срез 1 (partial unique related_group_member.legal_entity_id) |
закрыт решением владельца 04.10.2026: ЮЛ только в одной ГК, одновременно — в одной ГСК и одной ГСЗ. DBML v5.14: related_group_member.kind + partial unique (legal_entity_id, kind) WHERE is_current (отменяет ANL-030); выгрузка ЕЦП это подтверждает (флаги «входит в ГСК» и «входит в ГСЗ» одновременно) |
| OQ-33 | «ID ГСК/ГСЗ в CRM» в формах (51-03, 51-04, 77-02; пример «3-9CGSK01» в формате ЕЦП CRM): у related_group в DBML только внутренний code (GSK-001). Есть ли у ГСК/ГСЗ идентификатор в CRM? |
макет [77-02] | срез 1, DBML | закрыт реализацией Среза 1: related_group.code генерируется системой (GSK-NNNNNN/GSZ-NNNNNN), ID из CRM не вводится и не импортируется (в выгрузке ЕЦП его нет — OQ-91) |
| OQ-34 | «Удалить» ГСК в меню строки (77-01): физическое удаление или закрытие с историей? В DBML у related_group нет флага/истории удаления |
макет [77-01] | срез 1 | закрыт реализацией Среза 1 (ASSUMPTION подтверждено отсутствием возражений): удаляется только пустая ГСК/ГСЗ (409 GROUP_NOT_EMPTY), состав сохраняется в before_value аудита; группа с FK из консолидаций — follow-up GROUP_IN_USE (arm-core#8, T8.1) |
| OQ-35 | База процента отклонения: в таблице корректировки % считается к сумме стороны ЮЛ-1 (−79,77 % для 1 000 ↔ 202,32, 31-02-02), в «Подтверждении КФО клиента» — к версии банка (ст. 2, 31-06); DBML и Методика (сноска 14) — к меньшей сумме. Что считать правильным для correction.deviation_percent и client_kfo_line.deviation_percent? |
макет [31-02] [31-06] vs DBML, Методика | срезы 5–6 | допущение: к меньшей (Методика); макет считать неточным |
| OQ-36 | Экран проверки показывает строки формы без кода («в том числе: Основные средства, пригодные к использованию…», 41); DBML хранит только строки с кодом (financial_statement_line), контракт требует line_code. Хранить ли расшифровочные строки без кода (и как их показывать)? |
макет [41] vs DBML, контракт | срез 3, контракт v1.x | допущение: строки без кода не хранить и не показывать в первой очереди |
| OQ-37 | Строка «Гудвилл 1105» (ФСБУ 4/2023) в распознанной форме (41) vs строка kfo_result_line.line_code = 'GOODWILL' в разделе I КФО: использовать код 1105 или служебный GOODWILL? Влияет на шаблон строк на фронте и на формулы §7 (1100 = … + 1105?) |
макет [41], ФСБУ 4/2023 | срез 3, 6 | допущение: хранить код 1105 как пришёл; строка КФО «GOODWILL» — отдельная, уточнить у методологов |
| OQ-38 | ЕХ в макете показывает и неверифицированные консолидации («Консолидация 2025 К4», без даты верификации, 62 [83-01]); ЕХ ТЗ 6.2 — срез пишется только после «Верифицирован». Показывать ли в ЛК ЕХ незавершённые консолидации (как «в работе») или только ex_snapshot? |
макет [62] [83-01] vs ЕХ ТЗ 6.2 | срез 7 | допущение: только срезы ex_snapshot; незавершённые — в разделе «Отчётность» |
| OQ-39 | Состав «сформированной отчётности» в ЕХ (83-01): КФО .xlsx, Шахматка .xlsx и «Консолидированная отчетность .pdf» с датой «Сформирован» и подписями Утвердил/Верифицировал. Нужен ли PDF-экспорт КФО и какие ещё артефакты (сводная ОСВ, корректировки, гудвилл — ЕХ ТЗ 5.2) показывать? |
макет [83-01] vs ЕХ ТЗ 5.2 | срез 7 | допущение: xlsx из payload на лету для всех item_type; PDF — не делать до решения |
| OQ-40 | «Квартальная дата» в журнале для консолидации 2024 К1 показана как 01.01.2024 (43-02); по правилам банка 1 квартал 2024 = отчётная дата 01.04.2024 (consolidation.reporting_date). Что выводить? |
макет [43-02] | срез 4, 7 | допущение принято и реализовано в Срезе 4 (06.10.2026): consolidation.reporting_date по правилу банка (Q1 → 01.04.Y, Q2 → 01.07.Y, Q3 → 01.10.Y, Q4 → 01.01.Y+1), domain/consolidation_periods.reporting_date; макет неточен |
| OQ-41 | Экран «ЮЛ вне ГК» (61, заголовок «Юридические лица») показывает все ЮЛ с заполненной колонкой ГК, а не только монозаёмщиков. Это сводный реестр ЮЛ или фильтр company_group_id IS NULL? |
макет [61] | срез 1 | допущение: сводный реестр всех ЮЛ с фильтром «вне ГК», по умолчанию выключенным |
| OQ-42 | «Проверка распознавания» для ОСВ (xlsx) в макете отсутствует (строки doc-row--readonly, 39). Нужен ли отдельный экран проверки ОСВ (дерево строк, подсветка контрагентов) или проверка ОСВ только через сводную ОСВ (30)? |
макет [39] | срез 2 (UI) | закрыт решением владельца 05.10.2026: экран проверки ОСВ нужен (дерево строк, суммы, нераспознанные контрагенты); в макете его нет — проектируется в подсрезе 2d «UI первичных документов» |
| OQ-43 | Кнопка «К отчетности» неактивна у консолидаций в статусах «Инициация» и «Верификация РФ», активна у «Консолидация РФ» и «Верифицирован» (34). Каково правило доступности отчёта по статусу / роли? |
макет [34] | срез 4, 7 (policy) | допущение: отчёт доступен с CONSOLIDATION_RF и далее для всех ролей с CONSOLIDATION_VIEW; «Инициация» — только карточка |
| OQ-44 | «Добавить версию» создаёт версию сразу без модалки (43-01), копируя периметр; нужно ли подтверждение и выбор сценария/подхода новой версии? |
макет [43-01] | срез 7 | допущение: без модалки, периметр и сценарий копируются из предыдущей версии |
| OQ-45 | Контракт recognition-result.schema.json допускает data_type = NU (строки налогового учёта в ОСВ «БУ, НУ»), а enum DBML osv_data_type — только BU, CURRENCY, QUANTITY, SUM. Добавлять NU в DBML (v5.14) или не хранить строки НУ? |
контракт v1.0 vs DBML | срез 2 (маппинг osv_entry) |
закрыт решением владельца 05.10.2026: строки налогового учёта NU хранить в ядре: osv_data_type расширяется значением NU в DBML v5.15 (подсрез 2c, маппинг); контракт уже допускает NU |
| OQ-46 | Обязательны ли все ОСВ расширенного подхода (02, 03, 08, 44, 26, 91, 51, 52, 59, 80, 83, 86, 97, 001, 008, 009, 011) при approach = EXTENDED, или часть — «при наличии»? |
ТЗ 5.1; required_document_rule.is_mandatory |
срез 0 (seed), срез 3 (чек-лист) | допущение принято и применено в Срезе 0: все 27 строк EXTENDED в seed arm-core/api/seed/core/required_document_rules.yaml (маркер ASSUMPTION(OQ-46)) — is_mandatory = true; BASIC — 10 строк, все обязательные; смена правила — правка одного yaml; вопрос к методологам остаётся открытым до Среза 3 (чек-лист); источник: arm-core, предложения, раздел A |
| OQ-47 | Источник полного плана счетов 1С для seed chart_of_account: в фикстурах файла нет, «план счетов из бандла макета» не передан |
06-test-data.md §6, fixtures/README.md |
срез 0 (seed) | допущение принято и применено в Срезе 0: seed arm-core/api/seed/core/chart_of_accounts.csv — 122 строки (62 синтетических счёта Плана счетов 94н, 11 забалансовых, 49 субсчетов и подсубсчетов 1С БП 3.0, упомянутых в §4/§6); названия нестандартных субсчетов (66/67 .06/.22/.24/.26, 76.07.x, 76.08/.28/.38) выведены по их роли в §4 правил, а не взяты из справочника 1С; названия-заглушки заменить по справочнику 1С клиента, остальное добавляет администратор (ARCH-004); источник: arm-core, предложения, раздел A |
| OQ-48 | Правило элиминации № 1 (§4), сторона Б «76.07.1 − 76.07.5 − 76.07.9»: 76.07.5 и 76.07.9 не потомки 76.07.1, поэтому «вычитание» чисто информативно. Так и задумано? | 05-domain-rules.md §4 (правило 1); seed elimination_rules_v1.yaml; arm-core: предл. 1 (T14.1) |
срез 5 (пары ВГО) | допущение принято: исключения 76.07.5/76.07.9 на стороне Б хранятся как is_excluded и не влияют на сумму (76.07.1 их не содержит); алгоритм на них не опирается; подтвердить с методологами |
| OQ-49 | Правило № 1, сторона А: резервный счёт 01 лежит в одном списке с 03 и 76.07.9 — приоритета нет. Как выбирать «03, а если у А нет ОСВ 03 — 01»? Что значит «результат в строку ППА» (см. OQ-11)? | 05-domain-rules.md §4 (правило 1: «01 — резервный»); seed elimination_rules_v1.yaml; arm-core: предл. 2 (T14.2) |
срез 5–6 | допущение принято: в seed accounts: ["03", "76.07.9", "01"] без порядка; fallback «берём 01, если нет ОСВ 03» — в comment правила; логику приоритета реализует срез 5, до решения она не реализуется |
| OQ-50 | Составные коды статей КФО 1450/1550 (правило 1) и 1310/1350 (правило 7, гудвилл) хранятся в elimination_rule.article_2_line одной строкой с «/». Как их потреблять: делить по «/» и подбирать строку, которая есть в ББ Б, или хранить две строки? |
05-domain-rules.md §4 (правила 1, 7); seed elimination_rules_v1.yaml; arm-core: предл. 3 (T14.3) |
срез 5–6; кандидат DBML v5.14 (note колонки) | допущение принято: хранится как в §4, потребитель делит по «/»; предложение — зафиксировать это в note article_2_line в DBML v5.14 (версию не создавали) |
| OQ-51 | required_document_rule: уникальный индекс (approach, document_type, account_id) при nullable account_id допускает дубли строк для не-ОСВ документов (NULL в PostgreSQL различны). Добавить NULLS NOT DISTINCT? |
DBML v5.13 (required_document_rule, Indexes); arm-core: предл. 4 (T8.3) |
срез 3 (чек-лист); кандидат DBML v5.14 | допущение принято: ORM и миграция 1:1 с DBML v5.13 (без NULLS NOT DISTINCT); предложение v5.14: [unique, note: 'NULLS NOT DISTINCT'], как у document |
| OQ-52 | Контракт v1.0, recognition-result-message: schema_version принимает только 1.x (^1\.\d+$), а result_schema_version — любой MAJOR (^\d+\.\d+$). Намеренно (файл результата версионируется отдельно) или небрежность? |
contracts/recognition-result-message.schema.json; arm-core: предл. 5 (T20.7) |
контракт v1.x; срез 2 (inbox, маппинг) | допущение принято: ядро повторяет схему буквально (любой MAJOR проходит валидацию); совместимость MAJOR результата проверяет маппинг Среза 2; уточнить у владельца контракта |
| OQ-53 | Контракт v1.0: схемы нигде не задают additionalProperties: false (кроме columns ОСВ). Считать ли неизвестные поля допустимыми расширениями? |
contracts/*.schema.json; arm-core: предл. 6 (T20) |
контракт v1.x; все три сервиса | допущение принято: модели ядра extra = "ignore" (строже схемы быть нельзя — ядро отвергнет совместимого пира); изменение — через MINOR контракта |
| OQ-54 | Параметры JWT РАИСА неизвестны: JWKS URL / публичный ключ, iss, aud, имя claim с email. Что в продуктивном контуре? Делать ли проверку aud/iss обязательной в проде? |
ТЗ 8.1, 03-architecture.md (SSO РАИСА); core/auth.py; arm-core: предл. 7 (T16.2) |
срез 1+ (прод-конфигурация) | допущение принято: настраиваются AUTH_JWKS_URL/AUTH_PUBLIC_KEY (взаимоисключающие), AUTH_ISSUER, AUTH_AUDIENCE, AUTH_EMAIL_CLAIM (по умолчанию email); при пустых AUTH_AUDIENCE/AUTH_ISSUER aud/iss не проверяются; предложение: в проде сделать aud обязательным и предупреждать при старте, если пусто |
| OQ-55 | В enum audit_action нет действия для смены статуса задания распознавания (recognition_job). Добавлять ли (DBML v5.14) и нужен ли аудит обмена вообще? |
DBML v5.13 audit_action; arm-core: предл. 8 (R-7, design #20 arm-core) |
срез 2 (обмен); кандидат DBML v5.14 | допущение принято: Срез 0 не пишет audit_event из обмена recognition_job; сервис аудита (services/audit.py) готов и покрыт тестами; если нужно — действие в v5.14 и вызовы в recognition_exchange.py |
| OQ-56 | Append-only аудит обеспечен только на уровне кода (единственная точка записи, статический тест на UPDATE/DELETE по AuditEvent). Нужна ли защита на уровне БД (триггер или REVOKE UPDATE, DELETE для роли приложения)? Вопрос поднят дважды (в том числе финальным ревью ветки). |
инвариант «Append-only аудит» (../CLAUDE.md); services/audit.py; arm-core: предл. 9 и дополнение к нему (задача 21) |
миграции, права БД; платформа | решено человеком 04.10.2026: осознанный техдолг. Append-only на уровне кода и тестов; защита на уровне БД (триггер или REVOKE UPDATE, DELETE) — позже, вместе с платформой, до прода |
| OQ-57 | Счётчик повторных публикаций запроса (ACK_TIMEOUT_SECONDS × OUTBOX_MAX_REPUBLISH) хранится в recognition_job.metrics.republish_count (jsonb), колонки в DBML нет; при каждой повторной публикации published_at перезаписывается временем последней, время первой не сохраняется. Нужны ли колонка счётчика и время первой публикации (v5.14)? |
services/recognition_exchange.py (design #6 arm-core); arm-core: предл. 10 (T23, T23.1); docs: O-19 (задача 4 фазы 2) |
срез replay; кандидат DBML v5.14 | допущение принято: счётчик в metrics.republish_count, attempt при повторе не растёт; published_at — время последней публикации (dlq через (OUTBOX_MAX_REPUBLISH+1)×ACK_TIMEOUT_SECONDS ≈ 120 с, описано в dev/README.md); при будущем replay счётчик и reply_deadline надо сбрасывать |
| OQ-58 | Семантика сроков и ошибок обмена в ядре: (а) reply_deadline по умолчанию = now + 5 мин для обоих pipeline (tabular, image), если у задания NULL; в DBML срок есть только для OCR («OCR — 5 мин»), для парсера значения нет; (б) при невозможности собрать запрос по контракту задание уходит в error / INVALID_REQUEST; (в) при ACK_TIMEOUT × (MAX_REPUBLISH+1) ≥ 5 мин статус dlq недостижим (раньше сработает timeout). Верно ли? Какой срок у tabular-parser? |
DBML recognition_job.reply_deadline note; recognition_exchange.py (DEFAULT_REPLY_TIMEOUT); arm-core: предл. 11 и дополнение (T23.4) |
срез 2–3; настройка таймаутов | допущение принято: как описано (дефолты 30 с × 3 укладываются в 5 мин); сроки по pipeline уточнить с командами парсера и шлюза |
| OQ-59 | Email пользователя сопоставляется с app_user.email по lower(email); при двух записях, различающихся регистром, — 403 (fail-closed). Добавить CHECK/уникальный индекс по lower(email) в DBML? |
core/auth.py, api/dependencies/auth.py; arm-core: предл. 12 (R-17, design #29 arm-core) |
срез 1 (админ-экран); кандидат DBML v5.14 | закрыт реализацией Среза 2c (06.10.2026, arm-core/openspec/changes/slice-2c-osv-ingest), иначе, чем предлагалось: индекс по lower(email) не добавлен (сверка ORM↔DBML не умеет expression-индексы); в DBML v5.15 app_user.email хранится в нижнем регистре (миграция 0003 приводит существующие, seed и администрирование нормализуют при записи), поэтому обычный UNIQUE регистронезависим по построению |
| OQ-60 | Seed --dev не защищён флагом DEBUG: создаёт dev-пользователей (в том числе ADMIN) на любой БД, на которую указывает .env, и перезаписывает существующего app_user с совпадающим email. Нужна ли защита? |
arm-core scripts/seed.py; arm-core: предл. 13 (T15.2, T15.4) |
срез 0 (процесс); прод-развёртывание | закрыт реализацией (arm-core PR #10, техдолг Среза 1, 05.10.2026): python -m scripts.seed --dev при DEBUG=false отказывает с кодом 2 до чтения seed и подключения к БД; run_seed(dev=True) для тестов без защиты |
| OQ-61 | Неверный метод на существующий API-маршрут (например POST /api/v1/me) отдаёт 404 вместо 405: SPA-монтирование на / перехватывает частичное совпадение, а /api/* в нём принудительно 404. Приемлемо? |
arm-core app.py (SPAStaticFiles); arm-core: предл. 14 (T18.2) |
API-контракт (клиенты, фронт) | закрыт реализацией (arm-core PR #10, техдолг Среза 1, 05.10.2026): неверный метод на существующий маршрут /api/v1/* — 405 с заголовком Allow («метод не поддерживается этим маршрутом»); несуществующий путь и dev-маршрут при выключенных флагах — 404 |
| OQ-62 | В образе arm-core нет фронта (Dockerfile не собирает web/), GET / отдаёт 307 на // и затем 404; редирект на // похож на дефект монтирования SPA при ROOT_PATH=/. Собирать web/ в образ в Срезе 0/1 и чинить редирект? |
arm-core Dockerfile, app.py; docs: O-10 (задача 3 фазы 2), dev/README.md |
срез 0 (compose), срез 1 (фронт) | частично закрыт (arm-core PR #10): редирект / → // устранён (причина — root_path="/", теперь нормализуется), GET / отдаёт index.html. Открыто: фронт не собирается в образ arm-core — follow-up RSHB/arm-core#12 |
| OQ-63 | web/package-lock.json: шаблон lock не содержит, raisa-* из Nexus — lock вне банка отличался бы; в Срезе 0 lock в .gitignore. npm install сообщает 6 high (npm audit) в зависимостях шаблона web. Коммитить ли lock; кто обновляет зависимости? |
arm-core web/; arm-core: предл. 15 (T25.1, R-23, design #35 arm-core) |
платформа, фронт; до прода | допущение принято: lock не в репозитории, зависимости не менялись; решение — с платформенной командой (Nexus, политика audit) |
| OQ-64 | Аудит LOGIN / LOGIN_FAILED (действия есть в audit_action) для JWT-аутентификации: в каком срезе и что считать «входом» — первый запрос с токеном, каждый запрос, отказ проверки токена? |
DBML audit_action; core/auth.py; arm-core: предл. 16 (финальное ревью) |
срез 1 (вход, админ-экран); аудит | закрыт реализацией (arm-core PR #10, техдолг Среза 1, 05.10.2026): LOGIN — выдача dev-токена и первый успешный запрос пользователя с токеном после старта процесса; LOGIN_FAILED — 401 (токен не принят, без user_id, причина без токена) и 403 аутентификации (email в description); раздел SESSION, отдельная транзакция; запрос без Authorization/не Bearer и 403 по праву — не события входа; LOGOUT не пишется (сессий на сервере нет) |
| OQ-65 | Шаблонные CORS allow_origins=["*"] + allow_credentials=True и ProxyHeadersMiddleware(trusted_hosts=["*"]): в проде любой клиент может подставить X-Forwarded-For, и audit_event.ip_address недостоверен. Какие origins и доверенные прокси на платформе? |
arm-core app.py (шаблон команды); arm-core: предл. 17 (финальное ревью) |
прод-конфигурация; достоверность аудита | частично закрыт (arm-core PR #10): CORS при DEBUG=true — * без credentials, при DEBUG=false — CORS_ORIGINS (пусто — кросс-доменные запрещены); X-Forwarded-For только от FORWARDED_ALLOW_IPS (по умолчанию 127.0.0.1). Открыто (к платформе): перечень доверенных прокси прода — follow-up RSHB/arm-core#12 |
| OQ-66 | /docs и /openapi.json FastAPI открыты без аутентификации, в том числе в проде. Закрывать их (выключать при DEBUG=false) или оставить? |
arm-core app.py (шаблон); arm-core: предл. 18 (финальное ревью) |
прод-конфигурация; безопасность | закрыт реализацией (arm-core PR #10, техдолг Среза 1, 05.10.2026): /docs, /redoc, /openapi.json существуют только при DEBUG=true; в проде — 404 |
| OQ-67 | Inbox не обновляет stored_file.status (UPLOADED → PROCESSING → PROCESSED / ERROR) и stored_file.current_recognition_job_id по результату задания: после результата файл остаётся UPLOADED, current_recognition_job_id пуст. Кто и когда это делает в Срезе 2 — inbox или маппер, в одной транзакции? |
DBML stored_file, file_status; recognition_exchange.py; arm-core: предл. 19 (финальное ревью); docs: O-18 (задача 4 фазы 2) |
срез 2 (загрузка и маппинг) | закрыт реализацией Среза 2c (06.10.2026, arm-core/openspec/changes/slice-2c-osv-ingest): статус файла меняет код, проверивший current_recognition_job_id == job.id (services/file_status.py): ack/результат — PROCESSING, error/timeout/dlq — ERROR (inbox, вахтёр, публикатор), успешный маппинг — PROCESSED, автозамена — SUPERSEDED; current_recognition_job_id ставит загрузка. См. 05-domain-rules.md §9.2, §9.5 |
| OQ-68 | Конверт error у scan-gateway публикуется без metrics, поэтому recognition_job.metrics = NULL (у tabular-parser в metrics есть duration_ms). Требовать ли metrics в конверте ошибки и единые поля метрик у обоих сервисов? |
scan-gateway core/worker.py; docs: O-18 (задача 4 фазы 2, сквозная проверка) |
срез 2–3 (наблюдаемость, контракт v1.x) | допущение принято: metrics в контракте необязательны, ядро хранит NULL; требование — решение владельца контракта |
| OQ-69 | Обработчик recognition.result выполняется последовательно в callback подписки NATS; длинный маппер Среза 2 задержит следующие сообщения, ack сервиса не успеет за ACK_TIMEOUT_SECONDS (30 с) — ложный republish, затем dlq. Предложение: развести быстрый inbox (фиксация сообщения и статуса) и отдельный цикл маппинга результата. |
arm-core core/nats.py (subscribe_results), recognition_exchange.py (handle_message, ResultMapper); arm-core: предл. 20 (финальное ревью) |
срез 2 (маппинг); архитектура обмена (D-6) | закрыт реализацией Среза 2c (06.10.2026, arm-core/openspec/changes/slice-2c-osv-ingest): inbox быстрый (фиксация сообщения и статусов, файл результата не читает), маппинг — отдельный фоновый цикл run_mapping_loop (MAPPING_POLL_SECONDS, MAPPING_MAX_ATTEMPTS); ack за ACK_TIMEOUT_SECONDS не задерживается. См. 03-architecture.md, «Цикл маппинга результата» |
| OQ-70 | Перед чтением файла результата маппер должен проверять, что ключ начинается с results/{file_id}/{job_id}/, а бакет — бакет роли results (иначе сервис может «подсунуть» чужой объект). Закрепить это в контракте и 03-architecture.md? |
контракт recognition-result-message (result_s3); arm-core _register_result_object (recognition_exchange.py); arm-core: предл. 21 (финальное ревью) |
срез 2 (маппинг); контракт v1.x | закрыт реализацией Среза 2c (06.10.2026, arm-core/openspec/changes/slice-2c-osv-ingest): цикл маппинга отвергает результат, если бакет не бакет хранилища или ключ не начинается с results/{file_id}/{job_id}/ (без ./../пустых сегментов) — файл ERROR, повтора нет; правило закреплено в 03-architecture.md («Схема ключей S3») |
| OQ-71 | Запрос без читаемых job_id / file_id / attempt остаётся без ответа (конверт собрать нельзя): сервис логирует ошибку и отбрасывает запрос, ядро закрывает задание dlq / NO_ACK примерно через 120 с. Расходятся и реализации: при нечитаемом attempt tabular-parser берёт его из заголовка Nats-Msg-Id запроса или ничего не отправляет, scan-gateway подставляет attempt = 1 и публикует конверт. Оставить так, выровнять реализации или сделать file_id необязательным в конверте error (MINOR 1.1)? |
recognition-contract spec, сценарий «Нечитаемый запрос»; tabular-parser: P-1 (задача 4.3); docs: O-24, design #35 |
контракт v1.x; срез 2–3 | допущение принято: молчание сервиса (design #35); реализации выровнять в Срезе 2/3, влияние только на битые запросы |
| OQ-72 | Ack при несовместимой версии запроса: ранняя формулировка контракта и 03-architecture.md требовали «SCHEMA_MISMATCH и ack», оба сервиса публикуют error SCHEMA_MISMATCH без ack (ядро принимает результат из published, ack перед ним ничего не добавляет). Спека и документ приведены к поведению сервисов — подтвердить. |
tabular-parser: P-2; scan-gateway: п. 2 таблицы предложений (T12.13, R-16); docs: design #34 | контракт v1.x; срез 2 | допущение принято: без ack, результат SCHEMA_MISMATCH закрывает задание (design #34); если ядро начнёт требовать ack перед результатом — правка одной строки в воркерах и спеке |
| OQ-73 | Повторная попытка (attempt + 1) того же job_id после ошибки или результата: оба сервиса дедуплицируют по job_id (LRU DONE_CACHE_SIZE), поэтому replay с новым attempt получает только ack и не обрабатывается, на единственном инстансе задание повисает до reply_deadline → timeout. По 03-architecture.md replay — «сброс в queued с attempt + 1». Сделать ключ дедупликации (job_id, attempt); повтор во время обработки — только ack? |
tabular-parser: P-3 (задача 4.3); scan-gateway: п. 17 таблицы предложений (I-3, R-17); 03-architecture.md (replay) |
срез 3+ (replay); контракт v1.x | вопрос: допущение принято на Срез 0 — дедуп по job_id, ядро повторяет только тем же attempt; при одобрении — одна строка в воркере (ключ LRU); человеком 04.10.2026 не рассмотрен — вопрос остаётся открытым |
| OQ-74 | PUBLISH_RETRIES = одна первая попытка + до 3 повторов (всего до 4) с тем же Nats-Msg-Id; у scan-gateway тип PositiveInt (повторы нельзя отключить настройкой), у tabular-parser NonNegativeInt (0 допустим); паузы 0,5 с и 2,0 с. Согласовать единую семантику (повторы или всего попыток) и тип? |
scan-gateway: п. 1 таблицы предложений (R-13, T11.1); docs: O-25, design #36 | контракт v1.x; настройки сервисов | допущение принято: «1 + 3 повтора» (design #36), расхождение «3 против 4» обмен не ломает (дедуп по Nats-Msg-Id); выровнять тип — косметика конфигурации |
| OQ-75 | reply_deadline проверяет только scan-gateway (error TIMEOUT до отправки в ИС и потолок ожидания ответа); tabular-parser обрабатывает просроченный запрос. Пример contracts/examples/request-image.json имеет reply_deadline в прошлом (2026-10-03T12:45:00Z) и даёт TIMEOUT при ручном прогоне. Единая обязанность сервисов и свежая дата примера? |
scan-gateway: п. 12 таблицы предложений (O-e); docs: O-26, design #37 | контракт v1.x; срез 2 | допущение принято: срок — дело сервиса, пример — иллюстрация формы (оговорка в spec и 03-architecture.md, design #37); обновить дату можно одним PR с пересинхронизацией копий в трёх репозиториях |
| OQ-76 | Обе реальные фикстуры fixtures/ocr (aep_5.json, aep_8_form4.json) — ответы-ошибки ИС (error.code = INVALID_DOCUMENT, пустой table_structure). Успешный путь image (done / needs_check, файл в results/…, s3_object с bucket_role = results) сквозной проверкой не покрыт; в scan-gateway проверяется только синтетическим ответом. Нужны образцы успешных ответов Content AI. |
scan-gateway: п. 3 таблицы предложений (O-1, R-10, design #32); docs: O-15 (задача 4 фазы 2), design #31 | срез 3 (OQ-13/14, нормализатор); fixtures | частично закрыт человеком 04.10.2026: добавлены реальные образцы — aep_3.json (успех: баланс 42 строки + ОФР 18 строк), aep_3_ofr.json (второй ответ ИС по тому же файлу, INVALID_DOCUMENT), aep_6.json (успех: баланс 122 строки + ОФР 49 строк); PR в arm-ka-fixtures. Успешный путь image на реальных ответах — Срез 3 (нормализатор); транспорт scan-gateway проверяется на них в Срезе 0 (склейка нескольких json, NOT_IMPLEMENTED) |
| OQ-77 | §11 05-domain-rules.md описывает data_ocr[] (список), а в фикстурах data_ocr — объект с полями label/organization/inn/units/date/table_structure. Какой формат фактический на стенде ИС (связано с OQ-13, OQ-14)? |
05-domain-rules.md §11; scan-gateway: п. 4 таблицы предложений (O-1, T3.2) |
срез 3 (нормализатор) | закрыт человеком 04.10.2026 (по aep_3.json, aep_6.json): data_ocr — объект, один на json-файл (label, organization, inn, units, date — {form_name, confidence}; table_structure — список таблиц {label, rows[]}, одна таблица = один документ БС/ОФР, в одном json их может быть несколько). Успех — error = {code: "", message: ""} (не null!), ошибка — непустой code и пустой table_structure. §11 05-domain-rules.md исправлен. Уточнение человека 04.10.2026: разбиение одной формы на несколько таблиц (сегменты) в образцах не встречалось; наблюдается только несколько таблиц разных форм в одном json (баланс + ОФР). Нештатный случай (повтор label) — NEEDS_CHECK, правило уточнить в Срезе 3 по реальным данным |
| OQ-78 | Шлюз читает ровно <output>/<job_id>/<job_id>.json (спека); старый код читал все файлы каталога Output/<id>/. Какие файлы и когда реальная ИС кладёт в Output, в каком порядке (момент готовности)? |
scan-gateway: п. 5 таблицы предложений (T8.5) | срез 3 (стенд ИС) | закрыт человеком 04.10.2026: ИС кладёт все распознанные документы одновременно в каталог <job_id>, по одному json на документ (пример: aep_3.json + aep_3_ofr.json); шлюз читает все json каталога и склеивает в один ответ (готовность — по стабилизации набора файлов, SCAN_SETTLE_SECONDS); ошибка одного документа не отменяет остальные (metrics.warnings). Реализуется в Срезе 0 (доработка PR scan-gateway #3) |
| OQ-79 | Временный файл .<имя>.part лежит в Input (атомарная запись .part + rename): реальная ИС без маски может забрать недописанный файл. Уточнить у владельцев ИС маску и поведение или перенести временный файл в служебный подкаталог рядом с Input? |
scan-gateway: п. 6 таблицы предложений (R-5, T7/T8) | срез 3 (стенд ИС) | закрыт человеком 04.10.2026: временный файл записи уходит из Input в служебный каталог рядом (SCAN_TMP_DIR, по умолчанию Tmp, на той же шаре — rename остаётся атомарным). Реализуется в Срезе 0 (доработка PR scan-gateway #3) |
| OQ-80 | make up расходится с эталоном (scan-gateway и dev/Makefile.common): в tabular-parser нет minio-init — на свежем томе бакета arm-ka нет, test_s3_integration даёт NoSuchBucket, хотя README сервиса обещает бакет; в arm-core up -d без --wait и без --project-name (имя в compose фиксировано, безвредно). Также check-full в arm-core и tabular-parser — через пререквизиты (порядок при make -j не гарантирован), в scan-gateway — через $(MAKE); lint arm-core включает mypy scripts. Подключить include ../docs/dev/Makefile.common в сервисах? |
tabular-parser: Makefile (цель up); scan-gateway: п. 18 таблицы предложений (I-1); docs: O-1 и O-2 (задача 1 фазы 2), O-4 и O-5 (задача 2 фазы 2), dev/checks.md, dev/Makefile.common |
срез 0 (процесс), все сервисы | допущение принято: эталон — scan-gateway, расхождения описаны в dev/checks.md, Makefile сервисов не правились (design #22, #24); выровнять — правкой Makefile сервисов (follow-up) |
| OQ-81 | Переменные ARM_KA_DOCS / ARM_KA_FIXTURES поддержаны неравномерно: arm-core — обе; tabular-parser — только ARM_KA_DOCS; scan-gateway — ни одной (жёстко ../docs, ../../fixtures/ocr). Текст причины skip («fixtures недоступен: положите репозиторий…») не содержит строки «ARM_KA_FIXTURES недоступен» из сценария «Нет фикстур» spec quality-checks; для docs-теста текст тоже без ARM_KA_DOCS. Нарушение spec — править сервисы? |
scan-gateway и tabular-parser (тесты, Makefile); docs: O-3 (задача 1 фазы 2), dev/checks.md; spec quality-checks |
срез 0 (процесс), все сервисы | допущение принято: расхождение описано в dev/checks.md; правка тестов и Makefile в scan-gateway и tabular-parser — follow-up человеку (сервисы не правились); частично закрыт (Срез 2a, tabular-parser): хелпер tests/fixtures_helpers.py поддерживает ARM_KA_FIXTURES (текст skip называет переменную и путь), ARM_KA_DOCS поддержан в тесте идентичности контракта; scan-gateway — по-прежнему ни одной переменной, остаётся follow-up человеку |
| OQ-82 | .dockerignore в arm-core не исключает api/.env.test: файл попадает в образ (у tabular-parser и scan-gateway исключён). Исправлять? |
arm-core .dockerignore; docs: T3.2 (задача 3 фазы 2) |
срез 0 (образы), прод-сборка | закрыт реализацией (arm-core PR #10, техдолг Среза 1, 05.10.2026): .dockerignore исключает api/.env.* (включая .env.test и .env.example); настройки контейнера — только из окружения |
| OQ-83 | Контейнеры профиля services подписаны на те же subject и queue group общего NATS, что и экземпляры в интеграционных тестах сервисов (recognition.result / arm-core, *.request): NATS может отдать сообщение теста контейнеру и наоборот; интеграционные тесты scan-gateway и arm-core одновременно тоже делят NATS. Сделать queue group и subject-префикс настраиваемыми для тестов (например NATS_TEST_PREFIX) или поднимать отдельный NATS на тесты? |
scan-gateway: п. 11 таблицы предложений (T14.1, T11.8); docs: O-11 (задача 3 фазы 2; подтверждено ревью для tabular-parser и scan-gateway), dev/README.md |
срез 0 (процесс), интеграционные тесты | допущение принято: в dev/README.md предупреждение и команда stop для контейнеров профиля; не гонять make test-integration двух сервисов одновременно |
| OQ-84 | Запуск сервисов из venv без Nexus (make venv + python run_server.py из api/ с .env из docs/dev/.env.example, healthcheck по localhost) в Срезе 0 не проверялся ни в одном из трёх сервисов. Известно по чтению конфигов (design #44): .env.example в нынешнем виде отвергнут scan-gateway (OPENBAO__DATABASE__*) и tabular-parser (OPENBAO__DATABASE__*, OPENBAO__SMB__* — extra_forbidden), в нём нет OPENBAO__MINIO__MINIO_SECURE=false (по умолчанию true — https к http-MinIO). Прогнать сценарий, зафиксировать набор переменных и портов (SERVER__PORT, SCAN_HOTFOLDER_PATH для scan-gateway), решить, нужен ли отдельный .env.example на сервис, и снять пометку «черновик» с раздела dev/README.md? |
spec dev-environment, сценарий «Запуск из venv против compose без Nexus»; docs: design #43, dev/README.md («Запуск сервисов из venv без Nexus»); финальное ревью фазы 2: I-1 |
срез 0 (процесс, окружение разработки); кандидат в follow-up issue | вопрос: допущение Среза 0 — раздел README остаётся черновиком, сценарий не подтверждён (проверка требовала .venv внутри сервисных репозиториев, в фазе 2 доступных только для чтения) |
| OQ-85 | Возможен ли JetStream на платформе (серверный дедуп по Nats-Msg-Id, durable consumer'ы, redelivery вместо ack и повторной публикации на уровне приложения)? Сейчас NATS без JetStream (ответ платформы 03.10.2026); Срез 0 строит доставку на ack processing, повторной публикации ядром и дедупе по recognition_job.result_msg_id. Если JetStream подключить можно — меняются только publisher/subscriber (core/nats.py в трёх сервисах), outbox/inbox и контракт остаются; исчезают потеря результата при простое ядра и риск повторной обработки. |
решение человека 04.10.2026: «узнать у платформы и проверить позже»; design.md slice-0-skeleton (D-3, Open Questions п. 4); OQ-72, OQ-73 |
инфраструктура NATS (платформа); core/nats.py, воркеры сервисов |
вопрос к платформе (поставить напоминание, проверить после ревью Среза 0 / до Среза 3): допущение — без JetStream. 06.10.2026: RSHB/armka_file_worker (PDFWorker старого АРМ, проверен в контуре) тоже работает на core NATS (subscribe/publish + flush, без jetstream()), один JWT на все сервисы АРМ с правами по subject'ам; возможность включить JetStream по-прежнему не выяснена |
| OQ-86 | Фильтр видимости по ЗО (business_segment): сегмент есть только у app_user, у ГК/ЮЛ поля нет (DBML v5.14). Откуда брать сегмент ГК (из ЕЦП CRM? из РФ?), нужен ли вообще фильтр по ЗО в первой очереди? |
§10.1 правил; DBML v5.14 | срез 1 (policy), DBML v5.15 | допущение: в Срезе 1 ЗО не фильтрует; видимость — только по филиалу (ГО видит всё) |
| OQ-87 | Выгрузка ЕЦП содержит типы нерез. (354), ФЛ (180), нет инн (2); enum entity_type — только ЮЛ/ИП. Нужны ли нерезиденты и физлица в справочнике (контрагенты ОСВ, поручители)? |
docs/source/biq/ecp-export-lagal-entity.xlsx; §13.2 |
срез 1 (импорт), DBML | допущение: строки пропускаются с кодом UNSUPPORTED_TYPE, enum не расширяется |
| OQ-88 | Проверять ли контрольные разряды ИНН при импорте и вводе (сейчас только длина 10/12); в выгрузке 279 нечисловых значений (LV…, РНН…, нерез.) |
§13.2 | срез 1 | допущение: только длина; нечисловые → INVALID_INN |
| OQ-89 | «Базовый РФ ГК в CRM» в выгрузке содержит ЦКБ (1370 строк) и Головная организация: это головной офис (regional_office.is_head_office) или отдельные подразделения? Полный список — 53 значения, из них 51 «… РФ» |
ecp-export-lagal-entity.xlsx; §13.2 |
срез 1 (импорт, policy видимости) | допущение: создаются как обычные филиалы с именем из файла; маппинг на ГО — после ответа (миграция данных одной командой) |
| OQ-90 | Один ИНН в нескольких ГК выгрузки (215 случаев, напр. 7733288488 в 1-5AL0CC8 и 1-5A6WZN2): какая ГК основная? legal_entity.company_group_id — одна |
ecp-export-lagal-entity.xlsx; §13.2 |
срез 1 (импорт) | допущение: первая встретившаяся ГК выигрывает, остальные строки — LE_IN_OTHER_GROUP в отчёте; перевод — вручную |
| OQ-91 | В выгрузке ЕЦП только флаги «входит в ГСК/ГСЗ» без идентификатора группы: у ГК предполагается одна ГСК и одна ГСЗ (в макете у «РМ Агро» две ГСК). Как ЕЦП кодирует несколько ГСК в одной ГК и даёт ли ГСК имя/ID (OQ-33)? | ecp-export-lagal-entity.xlsx; §13.2; макет [77] |
срез 1 (импорт), срез 4 | допущение: импорт создаёт не более одной ГСК и одной ГСЗ на ГК с именем ГК; разделение — вручную Куратором/КА |
| OQ-92 | В выгрузке ЕЦП есть строки с непустым iD ГК, но пустыми «Организация»/«Группа компаний» (напр. строка 4034, ГК 1-KCKKHRK). Импорт сейчас создаёт сущность с пустым именем. Пропускать такие строки или брать имя из других строк той же ГК? |
ecp-export-lagal-entity.xlsx; §13.2; arm-core T9.6 |
срез 1 (до боевого импорта) | допущение: импортируются как есть; исправление — повторный импорт с update |
| OQ-93 | ИНН в xlsx бывает числом — ведущий ноль теряется ещё в Excel (0123456789 → 123456789, 9 цифр → INVALID_INN). Считать ли такие ИНН восстанавливаемыми (дополнять нулём слева до 10/12)? |
§13.1; arm-core T9.3 | срез 1 | допущение: не дополняем, строка → INVALID_INN |
| OQ-94 | Импорт с on_existing = update обновляет и архивные ГК (is_archived), не разархивируя их. Должен ли импорт разархивировать ГК, которая снова пришла из ЕЦП, или пропускать её? |
§13.2; arm-core T10.7 | срез 1 | допущение: обновляется без разархивации |
| OQ-95 | Код LE_IN_OTHER_GROUP в отчёте импорта называет ЮЛ (ИНН, имя) из ГК, которую филиальный пользователь с DIRECTORY_ADMIN видеть не должен. Допустимо ли раскрытие в отчёте импорта (право по seed только у ADMIN ГО)? |
§13.3; §10.1; arm-core T10.9 | срез 1 | допущение: отчёт импорта вне policy видимости, право даётся только ГО |
| OQ-96 | Граница «effective_date не раньше сегодня» (OQ-21) считается по UTC: для МСК правило сдвигается на 3 часа после полуночи. Какой часовой пояс — «банковский» МСК или UTC? |
§13.4; arm-core R-9 | срез 1 | закрыт решением владельца 05.10.2026: граница «сегодня» — по МСК. Код arm-core пока считает по UTC; перевод на МСК — follow-up RSHB/arm-core#8 |
| OQ-97 | Право DIRECTORY_DELETE по матрице §10 только у ADMIN: удалять алиасы и пустые ГСК/ГСЗ могут только администраторы, а не КА. Расширить право на KA_RF/KA_GO/CURATOR_GO? |
§10; arm-core O-6 | срез 1 (seed) | закрыт решением владельца 05.10.2026: право DIRECTORY_DELETE на КА не расширяется, удаляют алиасы и пустые ГСК/ГСЗ только ADMIN; матрица §10 без изменений |
| OQ-98 | Единый список кодов валют для строк data_type = CURRENCY («USD», «EUR» под контрагентом): достаточно ли ISO 4217-списка из rules/data_types.yaml (currency_codes: RUB, USD, EUR, CNY, KZT, BYN, GBP, CHF, JPY, TRY, AED)? Нужны ли нестандартные написания и коды, которые используют конфигурации 1С |
tabular-parser: design D-4, R-6, R-22 | срез 2c (маппинг CURRENCY-строк), словарь data_types.yaml |
допущение принято: ISO-перечень, дополняется строкой в YAML вместе с синтетикой; коды не входят в тест покрытия словарей (R-22) |
| OQ-99 | Несколько ОСВ одного счёта в одной книге (разные листы: два периода или две организации): парсер выдаёт по одному document на лист (locator = sheet:<имя>) и ничего не дедуплицирует. Как ядро выбирает/сопоставляет такие документы с периодом и организацией? |
tabular-parser: spec osv-parsing («Листы ОСВ находятся по содержимому») |
срез 2c (маппинг, document.period_check) |
допущение принято: каждый лист — отдельный документ, выбор — за ядром и аналитиком при проверке |
| OQ-100 | ИНН внутри текста строки контрагента («Полиграф, 3528315710», 20 строк в ОСВ 76 Счастливочной) и склеенная ячейка «ИНН/КПП»: извлекать ли ИНН в парсере или в ядре при резолве контрагента? Сейчас counterparty_inn берётся только из колонки ИНН (§1.3), counterparty_name = весь текст строки |
tabular-parser: R-15, R-17 (design №25, №27) | срез 2c (резолв counterparty_id по ИНН/имени) |
реализовано (решение владельца 07.10.2026, RSHB/arm-tabular-parser#9, PR #15, слит с #13): ИНН из конца текста названия контрагента → counterparty_inn, counterparty_name без ИНН (osv_headers.yaml: counterparty_inn_suffix); тесты — tests/unit/parser/test_classify.py (test_inn_is_extracted_from_counterparty_name), синтетика s17_inn_in_name; описание — 05-domain-rules.md §1.3 |
| OQ-101 | Числа с ведущим «+», «.5», «5.» парсер отвергает как нечисловой текст (null + предупреждение с номером строки). Встречаются ли такие записи в выгрузках других систем; принимать ли их |
tabular-parser: T7.3 | срез 2a+, словарь/правила numbers.py |
допущение принято: отвергаются; в реальных файлах не встречаются |
| OQ-102 | Нужен реальный xlsb из Excel: единственный fixtures/osv/synthetic/s13_no_outline_indent.xlsb собран тестовым писателем BIFF12 и не доказывает чтение файлов, сохранённых Excel (стили, другая раскладка записей). Получить xlsb у аналитиков или пересохранить s02 в Excel и заменить s13 по инструкции в README fixtures |
tabular-parser: R-9, O-7 | срез 2a (достоверность xlsb) | допущение принято: читаем pyxlsb без outline level и форматирования; проверка — на первом настоящем xlsb |
| OQ-103 | Счета с буквой в первом сегменте кода (забалансовые «МЦ.04», «ГТД», «НЕ.01.1») не распознаются как ACCOUNT: регулярка требует цифр в первом сегменте, строка станет контрагентом/группировкой по позиции. Нужны ли такие ОСВ, как их кодировать в chart_of_account |
tabular-parser: R-16 (design №26), classify.py |
срез 2c (план счетов), парсер | допущение принято: не распознаются; в 66 реальных файлах не встречаются |
| OQ-104 | Группировка «Валюта» в строке детализации (шапка варианта 2 «Детализация по субсчетам: …, Валюта») совпадает с меткой валюты шапки и порождает ложное предупреждение «валюта «» не распознана» (currency = null в обоих случаях). Править парсер (различать область шапки и строку детализации)? |
tabular-parser: O-8, T12.1 (синтетика s16) | срез 2a+ (header.py) |
допущение принято: дефект малозначим, предупреждение зафиксировано в golden-снимке s16 |
| OQ-105 | Сверка «сумма детей = родитель» по нетто (R-21): у родителя сальдо свёрнуто (счёт — одна сторона), у детей — развёрнуто. Подтвердить аналитиками, что нетто-свёртка родителя — штатное поведение 1С для всех версий выгрузки, а не артефакт двух организаций; остаточные риски RR-1…RR-4 и слепые зоны (перестановка сторон и одинаковое увеличение обеих сторон сальдо ребёнка под свёрнутым прямым родителем не ловятся ничем) перечислены в §1.4 | tabular-parser: R-21 (design №31), 05-domain-rules.md §1.4 |
срез 2a (контрольные суммы), срез 2c | допущение принято: нетто допустимо, если у родителя не более одной ненулевой стороны сальдо; 32 из 66 реальных файлов иначе не сходились |
| OQ-106 | «Итого = корень» сверяется с первой корневой строкой ACCOUNT (уровень 0). Лист с несколькими корнями уровня 0 (несколько счетов в одной ОСВ; ОСВ по субконто без строк-счетов) получает needs_check, хотя «Итого» может быть равна Σ корней. Сверять «Итого» с суммой корней? |
tabular-parser: финальное ревью Среза 2a, M-4 | срез 2a+ (контрольные суммы checks.py), чек-лист документов |
допущение принято: сверка с первым корнем; многокорневой лист → needs_check (аналитик проверяет вручную); в фикстурах таких листов нет |
| OQ-107 | При hierarchy=sums (дерево восстановлено по равенству «сумма детей = родитель») сверка «сумма детей = родитель» тавтологична — она не может не сойтись. Считать ли такой разбор needs_check (или отдельное предупреждение достаточно)? |
tabular-parser: финальное ревью Среза 2a, M-5 | срез 2a+ (статус результата), срез 2c (UI проверки) | допущение принято: статус — по контрольным суммам, стратегия видна в metrics.warnings (hierarchy=sums); на реальных файлах стратегия не выбирается (все — outline/trivial) |
| OQ-108 | Защита фазы чтения xlsx от XML-бомб (entity expansion) — defusedxml как новая зависимость сервиса — и иные лимиты памяти чтения. Остаточные риски после R-23/R-25: (1) бомба по объёму самих ячеек в пределах лимита распакованного zip — xlsx в 5 МБ с 2 млн обычных ячеек читается openpyxl 13 с и занимает 1,5 ГБ (замер ревьюера); (2) объём sharedStrings и стилей в тех же пределах; (3) entity expansion (от экспоненты «billion laughs» защищает только сам expat ≥ 2.4); (4) известный обход предсканирования (R-26): лист в части без .xml с ≥ 64 байтами ведущих пробелов и без XML-декларации не попадает в скан (файл 1,8 КБ → 27 с / 1,4 ГБ в openpyxl) — первый follow-up: убрать «sniff» и отдавать expat каждую некаталожную запись; (5) скан сам не ограничен размером чанка: «мусорная» XML-часть с атрибутом в 400 МБ → 1,3 ГБ / 1,2 с, 400 МБ тегов → 8,5 с CPU; (6) xls с очень многими крупными merged-диапазонами (> 10 000 ячеек каждый) — линейный просмотр индекса Sheet (≈ 4,4 мс на обращение), останавливает только PARSE_TIMEOUT_SECONDS: потолок на число таких диапазонов. Предскан — эшелон защиты, не граница безопасности; настоящая граница — лимит памяти контейнера / подпроцесс с RLIMIT. Рекомендация: жёсткий лимит памяти контейнера сервиса; и/или предпроверка объявленного <dimension> и потоковый счётчик ячеек <c>/строк sharedStrings (тем же expat-сканом, что объединения) с лимитом; и/или разбор книги в подпроцессе с RLIMIT_AS/таймаутом, который можно убить. Решение платформы |
tabular-parser: финальное ревью Среза 2a, I-1 (хвост), R-23, R-25 | tabular-parser (readers), платформа (зависимости и лимиты образа/пода) | допущение принято: действуют лимиты R-23/R-25 — распакованный zip ≤ 10 × PARSE_MAX_BYTES, площадь объединённых ячеек книги ≤ 200 000 (потоковый expat-скан всех XML-частей до openpyxl); объём ячеек, sharedStrings/стили и entity expansion не ограничены (README «Известные ограничения») |
| OQ-109 | Воркер tabular-parser скачивает объект S3 целиком и только потом проверяет PARSE_MAX_BYTES; проверять размер по stat до скачивания |
tabular-parser: финальное ревью Среза 2a, M-7 | tabular-parser (core/worker.py) — вне Среза 2a |
закрыт реализацией Среза 2c (06.10.2026, arm-core/openspec/changes/slice-2c-osv-ingest): ядро ограничивает размер файла на загрузке (UPLOAD_MAX_BYTES = 50 МиБ, как PARSE_MAX_BYTES парсера), поэтому в воркер большие объекты из ядра не попадают; проверка по stat до скачивания в tabular-parser остаётся как есть (дефенсивная, не требуется) |
| OQ-110 | Удаление файла после консолидаций: у document появятся FK из financial_statement_meta, consolidation_document, ex_snapshot_item, а DELETE /files/{id} Среза 2c их не чистит (в 2c строк в этих таблицах нет). ANL-020 запрещает физическое удаление после INITIATED. Что делать: запретить удаление файла, документ которого входит в версию консолидации, и/или каскадно чистить служебные таблицы (financial_statement_meta — в Срезе 3) |
arm-core Срез 2c: Task 10 (R-23, design №36, №48) | Срезы 3, 4 (расширить удаление или запрет) | закрыт для Среза 4 (06.10.2026): привязываются к версии только CONFIRMED документы незаменённых файлов, а файл с CONFIRMED документом удалить нельзя (FILE_HAS_CONFIRMED_DOCUMENTS) — удаление файла с документом в версии невозможно, расширять DELETE /files/{id} не нужно; автозамена снимает привязки документов заменённого файла (FILE_REPLACE.unbound_version_ids). Остаток: FK из financial_statement_meta и ex_snapshot_item — Срезы 3 и 7 |
| OQ-111 | Ретеншн S3-объектов при удалении файла: сейчас DELETE лишь ставит s3_object.expires_at = now(), объекты uploads/results физически не стираются (дополняет OQ-22). Кто и когда их удаляет — lifecycle-правило бакета по метаданным, фоновая задача ядра, ручная очистка? Объект, записанный в S3 до сбоя БД, остаётся сиротой |
arm-core Срез 2c: design D-10, №36 | платформа (lifecycle), Срез 2 → эксплуатация | допущение принято: бакет растёт; очистка — вне среза |
| OQ-112 | Остаточные объекты e2e-прогона: каждый запуск make e2e оставляет в MinIO dev-стенда ~2 объекта (uploads/…, results/… — следствие OQ-111) |
arm-core Срез 2c: Task 11 (O-5) | dev-стенд | допущение принято: чистить бакет arm-ka вручную (раздел «Сброс стенда» dev/qa-guide.md) |
| OQ-113 | Полнота плана счетов seed: 19 нормализованных кодов строк-счетов golden ОСВ отсутствуют в chart_of_account (01.01, 66.01, 76.01, 60.АВ, 76.ОТ и др.), поэтому у их строк account_id = NULL. Достаточно ли «администратор добавит» или seed должен включать полный набор субсчетов (включая буквенные) до Среза ВГО (§4–§5) |
arm-core Срез 2c: Task 8 (O-3) | Срезы 4–5 (пары счетов элиминации) | закрыт: допущение принято — выгрузка плана счетов 1С заказчика (fixtures/directories/chart-of-accounts-1c.xlsx, 488 строк, все 122 кода seed в ней есть) полная (arm-core #25); ранее: частично решён 06.10.2026, перенос в seed — arm-core#23 |
| OQ-114 | EntryOut (API строк ОСВ) не отдаёт is_modified/original_*: панели правки подсреза 2d нужен признак правки аналитика (инвариант «первичка не мутируется»: правка — рядом, с аудитом). Добавить поля в схему и контракт API при проектировании 2d |
arm-core Срез 2c: Task 9 (T9.7) | подсрез 2d | открыт: в 2c строк не правят, is_modified = false у всех |
| OQ-115 | Автозамена: если новый файл содержит лишь часть документов прежнего (прежний — ОСВ 60 и 51, новый — только 60), прежний уходит в SUPERSEDED целиком, документ 51 пропадает из списка файлов; в FILE_REPLACE.description о нём ничего нет. Подтвердить «замена файлом целиком» (OQ-06) для этого случая или заменять по документам |
arm-core Срез 2c: Task 8 (T8.4) | Срез 2d (список файлов), Срез 4 (комплект документов) | допущение принято: замена файлом целиком, документы заменённого файла сохраняются в БД |
| OQ-116 | ОСВ Q2–Q4, признанная нарастающей по данным Q1 (сальдо на начало корня равно сальдо Q1, §1.5), получает предупреждение маппинга и поэтому остаётся NEEDS_CHECK, а не RECOGNIZED (буквально D-7/D-9 спеки: RECOGNIZED только без предупреждений). Верно ли, что аналитик подтверждает такие документы вручную, или нарастающая по данным — штатный случай без предупреждения (OQ-02, OQ-05) |
arm-core Срез 2c: Task 7 (R-17, design №30) | Срез 2d (нагрузка на аналитика), §9.3 | допущение принято: NEEDS_CHECK; снять предупреждение — одна правка в result_mapping.py и правило статуса |
| OQ-117 | Индексы osv_entry(parent_entry_id), osv_entry(recognition_job_id), stored_file(current_recognition_job_id): DELETE /files/{id} (и очистка файла при повторном маппинге) делает seq scan на проверке self-FK — на временной таблице 200 тыс. строк удаление документа в 1000 строк 8,6 с без индекса и 16 мс с индексом (замер в psql на временной таблице, final review); при ~3 млн строк DELETE ≈ 2 мин → HTTP-таймаут и долгое удержание блокировок. Требуется DBML v5.16 + миграция 0004. Решает владелец (источник истины — DBML) |
arm-core Срез 2c: final review (I-1, R-24, design №51) | DELETE /files/{id}, повторный маппинг; подсрез 2d (UI удаления), прод |
закрыт в v5.15 (06.10.2026): индексы добавлены в DBML v5.15 (п. 4 changelog) и миграцию 0003_dbml_v5_15 arm-core — ix_osv_entry_parent_entry_id, ix_osv_entry_recognition_job_id, ix_stored_file_current_recognition_job_id; v5.16 и миграция 0004 не понадобились (v5.15 ещё не слит) |
| OQ-118 | Запрет загрузки и подтверждения документов после перехода версии в DATA_CORRECTION (ТЗ 03_9 A1: «после перехода к исправлению данных загрузка и подтверждение заблокированы»). В Срезе 4 загрузка и подтверждение блокировкой не ограничиваются и в DATA_CORRECTION разрешены |
решение владельца 06.10.2026; arm-core Срез 4 design D-6 | Срез 7 (маршрут) | допущение принято: в Срезе 4 запрета нет (только статус «в работе»); запрет вводится вместе с маршрутом |
| OQ-119 | Истечение блокировки «взять в работу»: DBML не задаёт срока, перехват — только вручную с причиной. Нужен ли авто-сброс брошенной блокировки (таймаут, «сессия умерла») | arm-core Срез 4 design D-7 | Срез 4, 7 (эксплуатация) | допущение принято: блокировка не истекает; освободить — DELETE …/lock владельцем или перехват |
| OQ-120 | document.period_check для документов, входящих в версии консолидации: подходящесть документа версии — функция (документ, версия), а один документ может входить в несколько версий (разные периметры и кварталы), поэтому записывать её в document.period_check нельзя. Показывать ли «подходит версиям: …» на карточке файла/документа |
arm-core Срез 4 design D-12 | карточка файла (подсрез 2d), §9.3 | допущение принято: period_check остаётся UNKNOWN; подходящесть видна в чек-листе версии |
| OQ-121 | Порог размера сводной ОСВ, после которого нужен кэш процесса: свод строится целиком в памяти на каждый запрос (страница, сводка, экспорт); синтетический тест (5,4 тыс. исходных строк → 1,8 тыс. строк свода) — 0,27 с / 8 запросов; на фикстурах РБ — 1385 строк свода (22 документа). Замер на реальной группе не делался; при десятках тысяч строк может потребоваться кэш (DECISION-5 запрещает кэш в БД) | arm-core Срез 4 design D-1, Risks | эксплуатация, Срез 5 (шахматка поверх свода) | допущение принято: без кэша; замер на реальной группе — до Среза 5 |
| OQ-122 | Ячейка чек-листа PROCESSING по всем недостающим столбцам ЮЛ, пока файл не смаплен: до маппинга счёт файла неизвестен, поэтому «в обработке» показывается у всех незакрытых правил ЮЛ. Устраивает ли такая неточность или нужна эвристика по имени файла |
arm-core Срез 4 design D-13, №6 | чек-лист, экраны 34–37 |
допущение принято: временный шум в чек-листе |
| OQ-123 | Общие строки osv_entry у нескольких версий: документ может входить в несколько версий одной ГК, ручное сопоставление контрагента хранится на строке osv_entry (DECISION-5) и поэтому меняет её для всех версий. Допустимо ли, или сопоставление должно быть «по версии» |
arm-core Срез 4 design Risks, D-10 | Срез 4, 7 (замороженные версии не меняются — они в ex_snapshot) |
допущение принято: сопоставление общее; before_value аудита хранит прежнее значение |
| OQ-124 | Q1-эквивалентность ОСВ (R-14, design №81): парсер помечает ОСВ за Q1 как нарастающую (период с начала года = Q1), поэтому документ ОСВ за Q1 в Срезе 4 равнозначен и нарастающему, и квартальному и подходит версиям любого квартала года (в версиях Q2+ он — квартальный источник Q1). ББ/ОФР за Q1 версиям Q2–Q4 не подходят (R-14a: баланс на конец Q1). Подтвердить; если ББ/ОФР за Q1 тоже должны подхватываться — снять условие в consolidation_periods.effective_cumulative |
arm-core Срез 4: Task 13, R-14/R-14a, design №81 | fits (привязка), select_sources (свод), чек-лист |
закрыт 07.10.2026 (владелец): ОСВ за Q1 используется и как нарастающая, и как квартальная (первое звено цепочки для версий Q2–Q4); ББ/ОФР за Q1 — только версии Q1 (R-14a) |
| OQ-125 | Версия за Q1 с квартальным ОСВ Q1: spec говорит «квартальный Q1 равнозначен нарастающему», но по design №50 квартальный Q1 идёт путём склейки (дубли ключа суммируются, None → 0, is_cumulative = false), а нарастающий Q1 отдаётся строка в строку (None сохраняется, дубли остаются раздельными). Различие видно только на дублях ключа и пустых ячейках. Приводить ли обе формы к одной |
arm-core Срез 4: Task 9 (T9.b), design №50 | consolidated_osv.build, версии за Q1 |
допущение принято: два пути; расхождение — только на дубле ключа внутри квартала и None в ячейках |
| OQ-126 | Область ручного сопоставления контрагента (R-11, design №62/№63): «строка версии» сужена до строк свода — group_type = COUNTERPARTY, data_type BU/SUM привязанных ОСВ-документов; строки NU/ACCOUNT дают 404 NOT_FOUND («строка ОСВ не найдена в версии») и в автоприменение не входят (§3 п. 2, R-3 Среза 2c: НУ не участвует). Цена: NU-строки того же контрагента остаются без counterparty_id. Буквальное чтение spec шире. Подтвердить |
arm-core Срез 4: Task 11, R-11 | counterparty_mapping, автоприменение |
допущение принято и реализовано |
| OQ-127 | Рекомендация кандидата (recommended) — только точное совпадение normalize_alias имени строки с нормализованным именем/алиасом ЮЛ. На реальных данных «Юг-Финанс / ПЕКАРНЯ СЧАСТЛИВОЧНАЯ» (так записан контрагент в ОСВ РБ) рекомендации не даёт: аналитик находит ЮЛ поиском по q. Нужна ли нечёткая рекомендация (токены, порядок слов, префикс «Юг-Финанс /») или достаточно ручного выбора и накопления алиасов |
arm-core Срез 4: Task 13 (O-3), design D-11 | GET …/counterparty-candidates |
допущение принято: рекомендация точная; после первого ручного сопоставления алиас закрепляет имя |
| OQ-128 | Неоднозначность дорезолва на лету при тёзке по имени: после ручного сопоставления создаётся алиас, но CounterpartyIndex.resolve на шаге «нормализованное имя/алиас» возвращает контрагента только при единственном кандидате — если ЮЛ-тёзка (по нормализованному name) есть рядом с владельцем нового алиаса, кандидатов двое, резолв неоднозначен и строки без сохранённого counterparty_id остаются несопоставленными. ТЗ DOC-221278: «алиас однозначен» — решает ли это, или алиас должен иметь приоритет над именем |
arm-core Срез 4: Task 11 (T11.9) | дорезолв свода (D-3), CounterpartyIndex |
открыт: допущение — резолв по Срезу 2c (неоднозначность → NULL); спасает автоприменение PROPAGATED (сохраняет counterparty_id) |
| OQ-129 | Формулировка сценария «сопоставление и автоприменение» (spec consolidated-osv): «unmapped_total падает на 1 + propagated» неверна на реальных данных. propagated считает записи osv_entry (включая пары-пробелы и строки, не дающие строк свода), а свод склеивает кварталы, поэтому unmapped_total падает на число склеенных строк свода (R-15, design №80). Уточнить текст spec при архивации |
arm-core Срез 4: Task 13 (T13.M1, R-15) | spec consolidated-osv при архивации change |
допущение принято: тест сверяет обе величины отдельно; текст spec уточнён в change, openspec/specs/ — при архивации |
| OQ-130 | ОСВ произвольного формата: ИНН в ячейке на строку выше названия контрагента (строка только с ИНН, без сумм). Сейчас такая строка уходит в OTHER, ИНН для резолва теряется. Решение владельца 07.10.2026: склеивать со следующей строкой контрагента; синтетические фикстуры на этот случай, на ИНН в тексте и на «ИНН/КПП» в одной ячейке | владелец, 07.10.2026 | tabular-parser §1.0 п. 4, §1.3; резолв контрагента в ядре | реализовано (RSHB/arm-tabular-parser#9, PR #15): строка только с ИНН над названием склеивается со следующей строкой контрагента (inn_only_row, предупреждение «ИНН … над строкой … отнесён к ней»), «ИНН/КПП» в одной ячейке (inn_kpp_cell); тесты — test_classify.py (test_inn_only_row_above_is_glued_to_next_counterparty, test_inn_with_kpp_in_one_column_cell_gives_inn), синтетика s18_inn_row_above, s19_inn_kpp_cell; 05-domain-rules.md §1.3 |
| OQ-131 | Скользящий период ОСВ «Октябрь 2024 г. – Сентябрь 2025 г.» (НПЗ 60/62/76, Агрокомплекс 76, osv-dkb/): парсер берёт первый месяц → 2024 Q4, нарастающий. Нужен разбор диапазона дат и признак «не с начала года»; как ядру относиться к такому документу при привязке к версии (не нарастающий и не квартальный) |
пакет ДКБ 06.10.2026, прогон парсера | tabular-parser periods.yaml/header.py; ядро fits (D-5 Среза 4) |
реализовано в парсере (tabular-parser PR #13 + Срез 2b): диапазон месяцев разбирается (periods.yaml: months, range), границы period_start/period_end/is_from_year_start — в контракте v1.2 (docs change 2b), скользящий период → period_quarter = null, is_cumulative = null и предупреждение «не с начала года» (05-domain-rules.md §1.5). Открыто для ядра: привязка скользящего документа к версии (допущение — не привязывать, показывать в чек-листе как «период не соответствует») |
| OQ-132 | Тождество сальдо по строкам (§1.4) на счёте 91 (и, вероятно, 90, 99): строки аналитики доходов/расходов несут только обороты, сальдо закрывается на 91.09 → needs_check на каждой ОСВ 91 (Цимлянский, Приманыческий). Исключать ли счета без сальдо по аналитике из проверки тождества строк |
пакет ДКБ 06.10.2026 | tabular-parser checks.py, статус документа |
открыт: допущение — оставить needs_check, аналитик подтверждает вручную; arm-tabular-parser#11 п. 2 |
| OQ-133 | ОСВ варианта 2 без строки корневого счёта: верхний уровень — субсчета (58.01, 58.03), «Итого» = их сумме, парсер сверяет «Итого» с первым корнем → needs_check на каждом листе (Новоросметалл, Агрокомплекс; уточняет OQ-106). Сверять «Итого» с суммой строк уровня 0 |
пакет ДКБ 06.10.2026 | tabular-parser checks.py |
реализовано (tabular-parser PR #13, arm-tabular-parser#11 п. 3): при нескольких корнях уровня 0 «Итого» сверяется с суммой строк уровня 0 (сальдо по колонкам либо по нетто, как у родителя, R-21); checks.py, 05-domain-rules.md §1.4 |
| OQ-134 | Шапка ОСВ без строки организации и с кодом/названием счёта в двух колонках («Счет: Код / Наименование», строка «Бухгалтерский | 9 месяцев 2025 г.»; Новоросметалл ОСВ 63): парсер не находит организацию, счёт, период, тип данных. Новый вариант заголовка — словарь или код | пакет ДКБ 06.10.2026 | tabular-parser detect.py/header.py, osv_headers.yaml |
реализовано (tabular-parser PR #13, arm-tabular-parser#11 п. 4): шапка без метки организации (организация — хвост названия ведомости), период и тип данных отдельными ячейками (periods.yaml: standalone, data_types.yaml: standalone_header), подписи «Код»/«Наименование» не группировки (osv_headers.yaml: column_captions), код счёта — из первой строки-счёта (с предупреждением); 05-domain-rules.md §1.1; тест test_header_without_labels_and_code_name_columns_are_found |
| OQ-135 | Отчёт 1С «Анализ счета» с отбором по контрагентам группы (fixtures/analysis-reports/, в именах файлов «ОСВ_…ПоКомпаниямГруппы»): не ОСВ, но содержит ВГО по корреспонденции счетов. Нужен ли отдельный тип документа для Среза 5, или ВГО считаются только из ОСВ |
пакет ДКБ 06.10.2026 | document_type, Срез 5 |
открыт: допущение — не разбирать; парсер отвечает INVALID_DOCUMENT |
| OQ-136 | ИНН организаций пакета ДКБ (Цимлянский, НКЗ, Новоросметалл, Приманыческий, Агрокомплекс) в шапках ОСВ отсутствуют; для теста консолидации по этим группам нужны ИНН и состав ГК/ГСК (выгрузка ЕЦП или список от ДКБ) | пакет ДКБ 06.10.2026 | seed/фикстуры справочников, тесты Среза 4–5 на новых группах | открыт |
| OQ-137 | Раскладка горячих папок ИС Сканирование: scan-gateway кладёт Input/<job_id>_<original_name> через временный Tmp/.<имя>.part, ничего не удаляет; PDFWorker старого АРМ (RSHB/armka_file_worker, проверен в контуре) кладёт Input/<task_id>.<ext> через временный <назначение>.<uuid>.tmp рядом (ИС игнорирует *.tmp), читает батч Output/<task_id>/*.json и удаляет Output/<task_id>/ и входящий файл после чтения. Какая раскладка актуальна? |
сравнение armka_file_worker (README «Файлы», clients.py) и scan-gateway/gateway/hotfolder.py, smb.py; OQ-78, OQ-79 |
scan-gateway (спека image-recognition-gateway, эмулятор, тесты) |
закрыт владельцем 06.10.2026: «правильно как в FileWorker» — имя <job_id>.<ext>, очистка Output/<job_id>/ и входящего файла после чтения; Output/<job_id>/*.json батчем и стабилизация набора (OQ-78) остаются. Реализация — arm-scan-gateway #6. Уточнение (Срез 3a, решение владельца 06.10.2026): вместо временного *.tmp рядом с назначением — .part в Tmp (в Input временных файлов не класть: ИС может подхватить недописанный файл, гарантии игнора *.tmp у нас нет; OQ-79 остаётся в силе), публикация без замены снимает .part сама; очистка — под флагом SCAN_CLEANUP, по умолчанию выключена (OQ-144) |
| OQ-138 | Единицы «тыс. руб.» в шапке против значений в рублях с копейками в выгрузках 1С (Унагранде Компани/Групп, НПЗ) и тысяч с дробями (Межениновская): кто и где приводит единицы. Примеры: книга НПЗ (npz/2025-Q3) — ББ needs_check, 1600 = 1700 расходится на 1.94 / 9.02 / 2.48 руб.; ОФР НПЗ и ОФР Унагранде (стр. 1) — needs_check по 2100/2200/2300 по той же причине; полные формы по факту сходятся только у Межениновской |
fixtures/statements-xlsx/README.md, tabular-parser design 2b D-6, D-7, решения №22–24 (R-11, R-13, R-14) |
Срез 3 (маппинг форм), §8 (КФО в тыс. руб.) | закрыт решением владельца 06.10.2026: единица «тыс. руб.» в шапке считается верной, значения отдаются как в файле; расхождения итогов в пределах округления (до 0,001 % наибольшего числа соотношения) статус не портят — мягкие нарушения «в пределах округления» (arm-tabular-parser#20). Книга НПЗ, ОФР стр. 1 Унагранде — done. Принимать ли расхождение и приводить ли единицы — решает КА в ядре (акцепт проверок, Срез 3) |
| OQ-139 | Форма без колонки кодов: сопоставление по именам строк из шаблонов 66н/ФСБУ (словарь statement_lines.yaml, line_code_confidence < 100)? Сейчас документ без строк и needs_check («колонка кодов не найдена») |
решение владельца 06.10.2026 (вопрос 6), tabular-parser design 2b D-4 | §12 п. 2, Срез 3 | открыт: нужен реальный файл без колонки кодов; допущение — не сопоставлять по именам; связано (Срез 3a): то же для PDF-пути — справочник кодов по редакциям, имена из OCR при пустой редакции (OQ-146, OQ-150) |
| OQ-140 | Страница-продолжение в отдельном файле (1С печатает ББ/ОФР постранично, один файл = страница): склейка в ядре по ЮЛ + период + тип формы; признак is_continuation / form_page в контракте (MINOR, две необязательных поля) |
решение владельца 06.10.2026 (вопрос 2), design 2b D-8 | §12 п. 9, Срез 3, контракт v1.x | допущение принято: страницы разных файлов не склеиваются; страница 2 без шапки — документ с пустой шапкой и needs_check, «загрузите форму одним файлом» |
| OQ-141 | Строка без кода, но с суммами в ОФР ФСБУ 4/2023 (НПЗ «Реализация иного имущества…»): хранить ли (OQ-36 расширяется с расшифровочных строк на строки с суммами) | fixtures/statements-xlsx/npz, design 2b D-6 |
Срез 3, DBML financial_statement_line (хранит только строки с кодом) |
допущение принято: строки без кода не отдаются, при наличии сумм — предупреждение «строк без кода с суммами пропущено: N» |
| OQ-142 | Образец ФСБУ 4/2023 (templates/ББ после 01.01.2025.xlsx): «АКТИВ» и код 1110 в одной строке из-за объединённых ячеек; шаблоны (коды есть, сумм и шапки нет) не данные. Нужен ли их разбор вообще |
fixtures/statements-xlsx/templates, design 2b D-8 |
§12 п. 10, тесты | допущение принято: шаблоны дают needs_check (шапка пустая, значения null); разбор шаблонов как источника данных не планируется, пока владелец не скажет иначе |
| OQ-143 | Лист продолжения с единственной кодовой строкой (книга НПЗ: лист ОФР с одной строкой 2400): min_codes = 3 → лист не распознаётся как форма и пропускается, строка 2400 теряется при needs_check документа. Понизить порог / принимать лист по близости к открытому документу ОФР / маркеру продолжения |
tabular-parser design 2b: решение №25, O-9 | §12 п. 2, п. 9; полнота ОФР НПЗ | реализовано в 2b (финальная волна, I-1): лист с 1–2 кодами форм после листа формы берётся как продолжение (min_codes_continuation = 1 в словаре) и присоединяется при совпадении числа колонок сумм и типа формы; не присоединён — needs_check и предупреждение; первая страница с 1–2 кодами формой не считается. Строка 2400 НПЗ в результате есть |
| OQ-144 | Права на удаление на SMB-шаре контура (Input — входящие файлы и .done, Output/<job_id>/, Tmp): можно ли сервису scan-gateway удалять файлы, и не удаляет ли их сама ИС Сканирование после обработки. Пока SCAN_CLEANUP=false — сервис ничего не удаляет (кроме .part, ставшего целью публикации записью без замены), в Input, Output и Tmp накапливаются остатки |
решение владельца 06.10.2026, change slice-3a-ocr-normalizer D-2, решения №26/№27 |
scan-gateway (горячие папки), эксплуатация стенда | открыт: допущение — очистка выключена; dev-compose включает SCAN_CLEANUP=true. Спросить платформу/владельцев ИС; при положительном ответе — включить флаг на стенде |
| OQ-145 | Редакция формы (66n / fsbu4) в контракте: нужна ли ядру редакция документа в конверте (необязательное поле statementDocument, кандидат MINOR 1.3), или ядро выбирает редакцию по report_date само, по тому же правилу §11.1 п. 10. Сейчас scan-gateway выбирает редакцию внутри и наружу её не отдаёт |
change slice-3a-ocr-normalizer, открытый вопрос 2 |
контракт docs/contracts/ (v1.3), Срез 3b (маппинг форм, справочник кодов в ядре) |
открыт: допущение — в конверт не передаётся, контракт остаётся v1.2; при положительном решении — MINOR 1.3 со ссылкой на этот OQ |
| OQ-146 | Словари §7 (проверки форм) и §12 (справочник кодов statement_lines.yaml, формулы ОФР) по редакциям 66н / ФСБУ 4/2023 для tabular-parser (2b) и ядра (3b): один набор правил на обе редакции или раздельные; как выбирать редакцию в ядре для Excel-пути |
change slice-3a-ocr-normalizer (справочник с редакциями только в scan-gateway), OQ-12, OQ-139 |
tabular-parser rules/*.yaml, ядро (Срез 3b), §7, §12 п. 2 |
открыт: допущение — scan-gateway ведёт свой api/rules/statement_lines.yaml; tabular-parser и ядро словарь по редакциям не используют; расширяет OQ-12 и OQ-139 |
| OQ-147 | Закрывающая скобка, распознанная OCR как } или ] ((7744536} в aep_3.json): дословный перенос parse_amount минус не ставит — сумма положительная, is_recognized = false. Расширить ли множество закрывающих скобок в §11.1 п. 7 после проверки на стенде |
change slice-3a-ocr-normalizer, решение №12; владелец 06.10.2026: «пока устроит как есть» |
scan-gateway normalizer/ocr/amounts.py, §11.1 п. 7 |
открыт: допущение — перенос дословный; знак теряется, строка подсвечена is_recognized = false, исходный текст — в raw/ |
| OQ-148 | Длинное/среднее тире «—»/«–» как прочерк: дословный parse_amount вырезает тире как «лишний» символ → сумма null, но is_recognized = false даже при confidence = 100 (дефис «-» даёт true). Лишний needs_check на формах с длинным тире. Считать ли прочерк любого вида распознанным |
change slice-3a-ocr-normalizer, решение №20 |
scan-gateway amounts.py, §11.1 п. 7, доля done на реальных сканах |
открыт: допущение — перенос дословный; правило уточняется после прогона на стенде |
| OQ-149 | Строки таблицы OCR, отброшенные классификацией (поведение источника, §11.1 п. 4), не восстанавливаются: с непустым нечисловым кодом (1150 :) и с пустым кодом, но с суммами (в aep_3.json — «БАЛАНС» и «Выручка»: код пуст, мусор 211(1 | стоит в колонке значения). Нормализатор выдаёт предупреждения строк с нечисловым кодом пропущено: N и строк без кода с суммами пропущено: N (как OQ-141 для Excel), документ — needs_check, содержимое строк остаётся в raw/. Восстанавливать ли такие строки по наименованию (словарь редакции) или по структуре формы |
fixtures/ocr/aep_3.json, aep_6.json, change slice-3a-ocr-normalizer, решение №29, §11.1 п. 4 |
scan-gateway rows.py/document.py, полнота форм, OQ-36, OQ-139, OQ-141 |
открыт: допущение — строки пропускаются с предупреждением; аналитик видит расхождение итогов в проверках 3b |
| OQ-150 | Состав редакции fsbu4 (ФСБУ 4/2023, отчётность с 2025 года) в api/rules/statement_lines.yaml scan-gateway: коды и наименования строк ББ и ОФР; источник — Приложения 3–4 к ФСБУ 4/2023 (черновой перечень кодов — шаблоны fixtures/statements-xlsx/templates/, OQ-142). Пока lines: {} |
change slice-3a-ocr-normalizer, D-7 |
scan-gateway api/rules/, эталоны fixtures/ocr/expected_normalize/ для документов 2025 года, статус done |
открыт: данные даёт владелец, код нормализатора не меняется; до заполнения документы 2025 года идут с именами из OCR, предупреждением «редакция fsbu4 не заполнена» и needs_check. Порядок заполнения — scan-gateway/README.md |
| OQ-151 | Эмулятор ИС Сканирование: файл, совпавший по sha256 с PDF из SCAN_FIXTURES_DIR, у которого нет JSON-набора (<имя>.json), отвечает «нет пары» (INVALID_DOCUMENT) и не уходит на SCAN_EMULATOR_DEFAULT. Пробел спеки эмулятора: считать ли такой файл промахом и отдавать набор по умолчанию |
change slice-3a-ocr-normalizer, спека scan-emulator |
scan-gateway scan_emulator, dev-стенд (dev/qa-guide.md §6) |
открыт: допущение — поведение как есть; PDF без JSON-пары в fixtures/ocr не класть |
| OQ-152 | Повтор кода строки в таблице OCR: остаётся первая строка (паритет с Excel-путём, §12 п. 5; решение №15), но в aep_6.json (ОФР) код 2410 повторён, оставленная первая строка пуста, а отброшенная содержит (649). Выбрать правило «первая строка с суммой» вместо «первая» (для aep_3 оно даёт иной результат, чем для aep_6 — нужна проверка на стенде) |
change slice-3a-ocr-normalizer, решение №34 (R-16), финальное ревью I-2 |
scan-gateway document.py, §11.1 п. 4, Excel-путь §12 п. 5 |
открыт: допущение — первая строка; предупреждение «код N повторяется» сигнализирует аналитику, сумма отброшенной строки остаётся в raw/ |
Служебные идентификаторы в DBML/аналитике: ANL-nnn — замечания аналитика к макету; ARCH-nnn — архитектурные решения;
DECISION-n — решения по модели; FIX-nn — исправления дефектов v4.