Docs по итогам 08.10: §1.4 (R-3), гайд фронта (#59/#60), OQ-155 «алгоритм ТЗ» (#64)

- 05-domain-rules §1.4: на счетах без сальдо по аналитике обороты «дети = родитель» сверяются всегда, сальдо — только
  у родителя с субсчетами; сопоставление счёта со списком (R-3 arm-tabular-parser#26, подтверждено владельцем).
- 05-domain-rules §5, 07-open-questions OQ-155: эталон РБ элиминирует полную сумму ВГО стороны, ТЗ — min по паре;
  решение владельца 08.10.2026 — алгоритм ТЗ; OQ-07 (1:1 или гибрид) открыт.
- 09-delivery-plan Срез 5: сверка с эталоном — только по точным парам.
- dev/frontend-api-guide: предупреждение автозамены в job.mapping_warnings (arm-core #60), алиас приоритетнее имени
  (arm-core #59); состояние — main d8bf5b2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2
This commit is contained in:
Andrey Epifancev 2026-10-08 01:16:35 +04:00
parent 15f722a60c
commit f24ca32360
4 changed files with 25 additions and 6 deletions

View File

@ -154,7 +154,12 @@ r11 Договоры ←
**Исключение — счета без сальдо по аналитике** (решение владельца 07.10.2026, OQ-132): у 91 и 99 строки аналитики несут
только обороты, сальдо закрывается на субсчёт закрытия (91.09), поэтому тождество строк на них не проверяется; список
счетов — данные в `rules/*.yaml` tabular-parser (начальный состав 91, 99; 90 не входит — внутри года у его субсчетов
сальдо есть). Проверки «сумма детей = родитель» и «Итого» на этих счетах сохраняются.
сальдо есть). Проверки «сумма детей = родитель» и «Итого» на этих счетах сохраняются в таком виде (R-3 PR
arm-tabular-parser#26, подтверждено владельцем 08.10.2026): **обороты** родителя сверяются с суммой детей всегда;
**сальдо** — только у родителя с дочерними субсчетами (91 → 91.01/91.02/91.09); у субсчёта, под которым одна аналитика,
сальдо с детьми не сверяется (у аналитики оно пусто, у субсчёта — равно оборотам). Счёт строки сопоставляется со
списком по ближайшему счёту ветки: запись `91` покрывает `91`, `91.01`, `91.09`, но не `910` и не `90.01`; строка
«Итого» проверяется как обычно.
- По каждому родителю: суммы родителя = Σ детей того же `data_type`. Строки CURRENCY/QUANTITY в суммирование не входят
и сами родителями не проверяются. **Обороты** сверяются по колонкам (дебет с дебетом, кредит с кредитом). **Сальдо**
(начало, конец) — по колонкам либо, **если у родителя сальдо свёрнуто** (не больше одной ненулевой стороны), по нетто
@ -419,6 +424,12 @@ closing_* = closing_* строки Qn
«несколько строк суммируются», DOC-223044 — «одна позиция = одна строка»). Рекомендация: агрегировать по
(компания, контрагент, счёт, сторона), сохраняя список `osv_entry.id` как детализацию.
**Алгоритм — по ТЗ** (решение владельца 08.10.2026, OQ-155): пары, `min`, порог 5 %, а не полная элиминация каждой
стороны, как в эталонном Excel. Агрегация перед матчингом (OQ-07) ещё не решена; на фикстурах несколько строк одной
компании по одному контрагенту и счёту возникают из группировки «Подразделение» (ОСВ 62 Счастливочной, Q3 2024: 62.01
«БП Производство» 106 311 116,46 и «БП Аренда» 1 401 330 на «Русский бисквит» при одной строке 60.01 у РБ). Варианты —
строго 1:1 по строкам (DOC-223044) или гибрид: сначала точные пары 1:1, затем агрегация остатка.
---
## §6. Шахматка и результирующая таблица (DOC-221277)

View File

@ -160,6 +160,7 @@
| 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/` |
| OQ-153 | Журналировать ли скачивание первички (`GET /files/{id}/content`) и CSV чек-листа (ТЗ 8.2: перечень событий журнала включает «выгрузку»)? Экспорт сводной ОСВ в xlsx пишет `audit_event(EXPORT)` (Срез 4), а скачивание исходного файла и CSV чек-листа в Срезе 3b — нет; комментарии к документу тоже не аудируются (`comment` хранит автора и время) | change `slice-3b-statements-api`, design D-10, D-11, D-13, решение №8; ТЗ 8.2 | `audit_event`, `GET /files/{id}/content`, `GET …/checklist?format=csv`, §9.5 п. 6 | **закрыт решением владельца 07.10.2026 (вариант А):** скачивание исходного файла (`GET /files/{id}/content`, это же и просмотр PDF) и CSV чек-листа в `audit_event` не пишутся; `EXPORT` пишет только экспорт сводной ОСВ в xlsx (Срез 4) |
| OQ-154 | Автозамена файла работает на уровне файла (ключ документа: тип, счёт/NULL, год, квартал, нарастающий): исправленный отдельный ОФР заменит весь прежний файл-книгу «ББ + ОФР» того же периода (`SUPERSEDED`), а подтверждённый ББ из неё отвяжется от версий «в работе». Допустима ли такая замена на уровне файла, или формы одного периода нужно заменять по документам | финальное ревью Среза 3b (заметка контроллера), change `slice-3b-statements-api` | Срез 3b / 5 | **закрыт решением владельца 07.10.2026 (вариант А):** замена остаётся на уровне файла (DBML `file_status.SUPERSEDED`, ТЗ 8.2); если у заменённого файла есть документы, ключа которых нет в новом, — предупреждение в `metrics.mapping_warnings` нового файла (какие документы выпали, был ли `CONFIRMED`, из каких версий сняты привязки). Правило — `05-domain-rules.md` §9.5 п. 3; реализация — RSHB/arm-core#58 |
| OQ-155 | Эталонная ручная консолидация (`fixtures/golden/Консолидация_Группа РБ.xlsx`, лист `01.10.2024`, «в т. ч. взаимные обязательства») элиминирует у каждой компании **полную сумму её строк на компании группы** (Счастливочная 1230 = 108 062 тыс. = 62.01 «БП Производство» 106 311 116,46 + 62.01 «БП Аренда» 1 401 330 + 76.06 350 000; РБ 1520 = 106 311 тыс.), а ТЗ (DOC-223044, §4–§5) — по **парам** с `min` и порогом 5 %. Какой алгоритм верен | разбор OQ перед Срезом 5 (08.10.2026), сверка эталона с golden ОСВ РБ/Счастливочной Q3 2024 | Срез 5 (матчинг §5, тест сверки с эталоном), Срез 6 (КФО) | **закрыт решением владельца 08.10.2026: алгоритм ТЗ** — пары, `min`, порог 5 % (§4–§5); эталон сверяется только по точным парам (106 311 116,46 по правилу № 6), асимметрия эталона (1 751 тыс. у Счастливочной) — ожидаемое расхождение, описывается в тесте. Связано: OQ-07 (строго 1:1 или гибрид — ждёт владельца), OQ-08 |
Служебные идентификаторы в DBML/аналитике: `ANL-nnn` — замечания аналитика к макету; `ARCH-nnn` — архитектурные решения;
`DECISION-n` — решения по модели; `FIX-nn` — исправления дефектов v4.

View File

@ -266,7 +266,9 @@ DATA_CORRECTION, истечение блокировки, `period_check`, пор
- UI: шахматка (ячейки с комментариями), таблица пар (авто/ручные/несопоставленные), таблица корректировок с
импортом/экспортом xlsx.
- Тест: пара 60.01 АО РБ ↔ 62.01 Счастливочная на 30.09.2024 = 106 311 116,46 (точный матч по правилу № 6); заём
42 млн 66.03 ↔ 58.03; сверка «в т. ч. взаимные обязательства» с `Консолидация_Группа РБ.xlsx` лист `01.10.2024`.
42 млн 66.03 ↔ 58.03; сверка «в т. ч. взаимные обязательства» с `Консолидация_Группа РБ.xlsx` лист `01.10.2024` —
**только по точным парам**: эталон элиминирует полную сумму ВГО каждой стороны, а Срез 5 — по алгоритму ТЗ (`min` по
паре); расхождение ожидаемо и описывается в тесте (OQ-155, решение владельца 08.10.2026).
## Срез 6 — КФО: результат, гудвилл, пороги, сравнение с клиентом

View File

@ -1,7 +1,7 @@
# Гайд фронтенд-разработчика: API arm-core
Состояние — `arm-core` на `main` от 07.10.2026 (коммит `99669cd`: после PR #46 «API до сводной ОСВ», #51 «Основной РФ объектом»,
#52 «КПП ЮЛ» и #53 «Схема БД настройкой»).
Состояние — `arm-core` на `main` от 08.10.2026 (коммит `d8bf5b2`: после PR #46 «API до сводной ОСВ», #51 «Основной РФ объектом»,
#52 «КПП ЮЛ», #53 «Схема БД настройкой», #59 «Алиас приоритетнее имени» и #60 «Предупреждение автозамены»).
Схема сверена с `GET /openapi.json` (62 операции на 49 путях), коды ошибок — с `api/src/services/*` и `arm-core/README.md` «Ошибки API».
Это **точка входа**: здесь всё, что нужно, чтобы писать клиент, — подключение, общие правила, каждый маршрут, ошибки, сценарии
@ -29,6 +29,8 @@
| Когда, PR | Что изменилось в API | Что сделать в клиенте |
|---|---|---|
| 08.10, arm-core #60 (#58) | **Предупреждение автозамены** (OQ-154): если новый файл заменил прежний целиком, а части документов прежнего в нём нет (книга «ББ + ОФР» → отдельный ОФР), в `job.mapping_warnings` **нового** файла приходит одна строка на заменённый файл: «автозамена: файл «…» (версия N) заменён этим файлом целиком, но в новом файле нет документов прежнего: …; привязки сняты с версий консолидации: …» — с пометкой `CONFIRMED` у подтверждённых документов. Схемы ответов не менялись; поведение замены прежнее | показать предупреждение в карточке нового файла заметно (не только в общем списке подсказок): аналитик должен догрузить выпавшие документы; текст не разбирать |
| 08.10, arm-core #59 (#57) | **Алиас приоритетнее имени** (OQ-128): при ЮЛ-тёзке по нормализованному имени строка с совпавшим алиасом теперь сопоставляется с владельцем алиаса (`match_kind = ALIAS`; в своде — `match_stored = false`, если найдено на лету). После ручного сопоставления `unmapped_total` может уменьшиться больше чем на 1 + `propagated` | после ручного сопоставления перечитывать и страницу, и `…/osv/summary` (как и раньше), не вычислять «Осталось N» на клиенте |
| 07.10, arm-core #52 (#45) | **КПП ЮЛ** (DBML v5.17, `context/05-domain-rules.md` §13.5): поле `kpp: string \| null` в `LegalEntityCreate`, `LegalEntityUpdate`, `LegalEntityOut`/`Detail`, `LegalEntityHistoryOut`. Формат — 9 символов (4 цифры, 2 цифры или латинские A–Z, 3 цифры; пробелы убираются, буквы — в верхний регистр), иначе 422 `INVALID_KPP`; у ИП КПП запрещён (422), у ЮЛ необязателен; уникален только ИНН. Поиск `q` в списке ЮЛ ищет и по КПП; смена типа на ИП у ЮЛ с КПП без очистки КПП — 422 `INVALID_KPP`; `kpp: null` в `PATCH` очищает КПП; в импорте ЕЦП — необязательная колонка «КПП» и код строки `INVALID_KPP` | поле КПП в формах ЮЛ и в карточке; в форме ИП поле скрыть; ошибку `INVALID_KPP` — на поле «КПП» |
| 07.10, arm-core #53 (#38) | Схема БД задаётся настройкой `DB_SCHEMA` — **API не изменился** | — |
| 07.10, arm-core #51 (#47) | «Основной РФ» приходит **объектом** `{id, name, is_head_office}` или `null`: `regional_office` в `LegalEntityOut`/`Detail`/`HistoryOut`, `base_regional_office` в `CompanyGroupOut`/`HistoryOut` (в т. ч. в ответах `POST`/`PATCH`/`move`/`withdraw`/`archive`). Поля `*_id` остались; неактивный филиал тоже отдаётся; входные схемы прежние (`*_id`) | брать имя филиала из объекта, а не склеивать с `GET /directories/regional-offices` |
@ -352,7 +354,9 @@ UI прячет элементы по `GET /me → permissions` (объедин
Опрос: перечитывайте `GET /legal-entities/{id}/files` (или `GET /files/{id}`), пока есть файлы `UPLOADED`/`PROCESSING`;
интервал 3–5 с. Предупреждения — `job.warnings` (сервис распознавания) и `job.mapping_warnings` (ядро), у документа —
`DocumentOut.warnings`; это подсказки, тексты не разбирать.
`DocumentOut.warnings`; это подсказки, тексты не разбирать. Предупреждение автозамены (строка `job.mapping_warnings`,
начинается с «автозамена:») — исключение по важности: в нём документы, которые выпали из заменённого файла и сняты с
версий консолидации, — показывать его заметно.
Документ:
@ -429,7 +433,8 @@ UI прячет элементы по `GET /me → permissions` (объедин
а не «что готово»: готовность — в ячейках `checklist`.
8. **Сводная ОСВ — чистая функция**, в БД не хранится; одна и та же для страницы, сводки и экспорта. `match_stored = false` —
контрагент найден на лету (в БД не записан). Склеенная из кварталов строка несёт несколько `entry_ids` (от раннего к
позднему), её `match_kind` — от последнего квартала.
позднему), её `match_kind` — от последнего квартала. На лету совпадение по алиасу приоритетнее совпадения по имени: при
ЮЛ-тёзке берётся владелец алиаса (`ALIAS`).
9. **Ручное сопоставление**: отправляйте **последнюю** `entry_id` строки (`entry_ids.at(-1)`), тогда в ответе `MANUAL`.
`propagated` — число записей `osv_entry`, а не строк свода. `row` может быть `null` (строка привязанного документа в свод не
попадает) — тогда перечитайте страницу и сводку. При `ALIAS_TAKEN` спросите пользователя и повторите с `move_alias: true`.