Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BJKMLHdxMwSUeYcdkwqX4W
40 KiB
40 KiB
Context
Сквозной дизайн — change docs unified-statement-normalization (design.md, specs/arm-core/spec.md, плана plans/2026-10-18-plan-3-arm-core.md). Здесь — решения уровня ядра для части А: где лежит код, как идёт миграция, какие допущения сделаны планом. Схема данных — ../docs/model/data-model-v5.19.dbml.
Goals / Non-Goals
Goals и Non-goals — см. proposal.md.
Decisions
- D-1. Часть А не зависит от пакета. До части Б §7 считается на лету по
api/rules/statement_checks.yamlи агрегату формы по коду (P3-14); временный код удаляется в B6. - D-2. Миграция только добавляет. Одна ревизия (0007): новые колонки, таблица, enum'ы, индексы и значения по умолчанию; §7 по существующим формам не пересчитывается (Р-24); откат останавливается на несовместимых данных (спека «Миграция DBML v5.19»).
- D-3.
origin = template⇔row_index IS NULL.line_code_original_value IS NULLдляoriginбольше не используется. - D-4. Порядок A5 — последним (R-1).
Decisions made without discussion
Решения, принятые без обсуждения с человеком на этапе плана и исполнения части А (маркеры P3-n — из плана 3; R-n — рулинги контроллера; Р-n — решения владельца, поверх плана).
| Дата | Решение | Обоснование | Цена ошибки |
|---|---|---|---|
| 2026-10-18 | P3-1. Миграция пишет header_source с ключами inn, report_date, units, organization_name: text — из inn_original_value, report_date_original_value, units_original_value, organization_name; confidence — inn_confidence у inn, у остальных null |
спека: «из *_original_value и inn_confidence»; прочих полей в v5.18 сырыми нет |
подсказка у поля старой формы беднее; формы старых времён на контуре удалены очисткой |
| 2026-10-18 | P3-2. organization_name_modified — false у всех форм (миграция и оба маппера), не NULL |
как inn_modified в маппере 1.x; API отдаёт bool |
одна строка UPDATE |
| 2026-10-18 | P3-3. needs_review_fields: маппер 1.x пишет [], миграция оставляет NULL, чтение NULL = [] |
спека миграции поле не заполняет | нет |
| 2026-10-18 | P3-4. Маппер 1.x тоже хранит вторую форму того же типа (Р-10); формы одного типа и одной пары period_year, period_quarter получают MAPPING_WARNING «в файле есть другая форма <тип> за тот же период» и по D-6 — NEEDS_CHECK (аналог Р-18) |
частичный индекс v5.19 больше не заставляет пропускать; одно правило хранения для обоих MAJOR | O-1: если владелец хочет прежний пропуск — вернуть ветку continue |
| 2026-10-18 | P3-5. Предупреждения маппинга листа ОСВ (повтор счёта, строки без родителя, нарастающая по данным Q1) пишутся ещё и в document_issue документа листа (warning, MAPPING_WARNING, info, core) при любом MAJOR; строки в metrics.mapping_warnings остаются |
спека: DocumentOut.issues ОСВ — «вместе с предупреждениями маппинга ядра этого листа» |
нет |
| 2026-10-18 | P3-6. file_ordinal объединённого документа DISCLOSURE / OTHER — индекс первого такого документа в documents[] |
несозданный документ «номер не занимает» | нет |
| 2026-10-18 | P3-7. Переопределено Р-40 (владелец, 18.10.2026): CODE_NOT_IN_DICTIONARY / CODE_NOT_IN_EDITION пишутся только для четырёхзначных кодов (пятизначные подстроки Excel не дают info; закрывает O-3). Остальное — как ниже. CODE_NOT_IN_DICTIONARY и CODE_NOT_IN_EDITION — одна запись на код документа (line_ids — все строки ok с кодом), пятизначные коды тоже проверяются; при приёме 1.x проверки ядра уровня info не пишутся |
спека для 1.x перечисляет только §7 и MAPPING_WARNING |
O-3 (поток info у пятизначных) |
| 2026-10-18 | P3-8. После правки записи core CODE_NOT_IN_DICTIONARY, CODE_NOT_IN_EDITION, NAME_MISMATCH и STATEMENTS_VERSION_MISMATCH пересчитываются (заменяются); NAME_MISMATCH не ставится строке с code_fixed; MAPPING_WARNING, RESULT_STATUS_INCONSISTENT, LEGACY_WARNING сохраняются |
код строки меняется правкой; страховка статуса — факт приёма | O-4 |
| 2026-10-18 | P3-9. Результат 2.0: значение с column_index вне колонок формы и повтор column_index колонки — первое остаётся, MAPPING_WARNING |
FK значения на колонку; уникальность (meta_id, column_index) |
нет |
| 2026-10-18 | P3-10. Части PATCH применяются в порядке lines → values → columns → header; вся адресация (422) проверяется до первой записи; UNKNOWN_CELL по line_code — по кодам строк ok с учётом назначений lines этого запроса; один line_id дважды в lines — 422 INVALID_LINE_PATCH |
атомарность и предсказуемость | нет |
| 2026-10-18 | P3-11. reference_name — первое имя кода в редакции ответа (edition по дате); нет редакции или кода — null |
спека: «первое имя кода в словаре»; edition ответа — по дате |
нет (Р-29) |
| 2026-10-18 | P3-12. (= Р-35, решение владельца.) Очистка отказывает, только если документ формы упомянут в ex_snapshot_item или привязан к версии в статусе «Верифицирована» или дальше: VERIFIED или ARCHIVED с ex_snapshot.source_version_id = версия; привязки к версиям в работе, на согласовании, REJECTED, CANCELLED (и архивированным отменённым) снимаются, статус версий не меняется |
решение владельца 18.10.2026 (Р-35): верифицированная версия read-only и заморожена в ЕХ, прочие — нет; на стенде версий дальше «в работе» нет | архивированная версия без снимка ЕХ (из CANCELLED) очистке не мешает — если бы мешала, владелец увидел бы лишний отказ |
| 2026-10-18 | P3-13. Коды выхода purge_statements.py: 0 — отчёт или выполнено, 1 — отказ (п. 2 спеки), 2 — ошибка аргументов |
как у check_dbml_vs_orm.py |
нет |
| 2026-10-18 | P3-14. До части Б §7 считает ядро на лету (statement_checks.yaml) по агрегату; checks.failed[].codes берутся из правила, line_ids — по агрегату |
часть А должна быть самосогласованной | временный код удаляется в B6 |
| 2026-10-18 | P3-15. Для условия RECOGNIZED 2.0 предупреждения файла берутся из metrics.warnings файла результата; в recognition_job.metrics.warnings — из конверта (inbox) |
маппер читает файл результата; по контракту наборы одинаковы | нет |
| 2026-10-18 | P3-16. (= Р-34, решение владельца.) Подтверждение кода (lines[] {confirmed: true}) ставит is_confirmed = true и needs_review = false, line_code_confidence и line_code_modified не меняет; назначение кода — confidence = 100, line_code_modified = true |
иначе исходная уверенность теряется, а снятие подтверждения не может вернуть needs_review (гайд §5.6.1: «при false — пересчитывается»); аналогия с ячейкой (is_modified не ставится); подтверждено владельцем 18.10.2026 (Р-34), спека arm-core, §9.5 п. 9 и гайд §5.6.1 приведены к этому |
нет |
| 2026-10-18 | P3-17. Порядок строк ответа: строки ok (с шаблонными) — по коду, затем row_index (NULL последними), затем id; за каждой строкой ok источника — её «вложения» по row_index: строки detail с этим родителем и строки unreadable / missing / detail без родителя, у которых она — ближайшая предыдущая по row_index строка ok источника; вложения без такой строки — в начале |
спека «Ответ GET …/statement» |
нет |
| 2026-10-18 | P3-18. Уточнено Р-41 (владелец): смена crm_id пишется в аудит как изменение группы (GROUP_CHANGE) — закрывает O-6. Изменение crm_id ГСК/ГСЗ пишет одно событие GROUP_CHANGE с before/after всех изменённых полей вместо MEMBERSHIP_CHANGE |
спека: «изменение — аудит GROUP_CHANGE» |
O-6 |
| 2026-10-18 | P3-19. Ядро зовёт у пакета choose_edition (engine.edition) и status_input_from_contract (engine.contract_document_status) вместо сборки StoredForm ради редакции и своего status_input_from_v2; to_contract ядру не нужен |
одно правило с сервисом и с recompute (план 1, P1-25, P1-27); пары (код, уверенный) — из строк ok результата по row_index |
при ошибке сборки пар редакция приёма разойдётся с редакцией после правки — тест B1 сравнивает их |
| 2026-10-18 | P3-20. header.source.<поле>.confidence — строка Decimal с двумя знаками ("61.54"), как прочие уверенности API; в JSONB header_source остаётся число как пришло |
фронт читает все уверенности одним способом (план 6, A8) | округление до сотых в подсказке; исходное число — в БД |
| 2026-10-18 | P3-21. StatementHeaderOut.okei — из financial_statement_meta.okei (колонка есть с v5.18), только чтение; в PATCH header не входит |
спека header контракта несёт okei, экран показывает issues с field = "okei" у поля |
нет |
| 2026-10-18 | P3-22. Справочник строк для подсказки кода — поле StatementOut.edition_lines [{code, name}], а не отдельный эндпоинт: данные уже загружены (LineCatalog, A6) при сборке ответа, нет нового маршрута, права, кэша и запроса на фронте; около 80 пар на форму (≈ 4 КБ) против строк формы с значениями; у формы без даты — коды всех редакций, по одному |
наименьший объём реализации в ядре и во фронте; закрывает пробел «у формы без даты подсказки нет» (план 6, O-3) | ответ GET тяжелее на ≈ 4 КБ; если понадобится справочник вне формы — отдельный эндпоинт поверх того же LineCatalog.edition_lines |
| 2026-10-18 | P3-23. DocumentBrief.issue_counts {error, info} — все записи document_issue документа (и relation / soft, в том числе акцептованные) по severity; один групповой запрос на файл |
панели файлов нужен признак «есть что смотреть» без запроса на каждый документ (план 6, O-4) | акцептованное нарушение продолжает помечать документ в панели — подробности на экране документа |
| 2026-10-18 | P3-24. Ошибки элемента lines[] (409 LINE_CODE_TAKEN, 422 LINE_CODE_REQUIRED, INVALID_LINE_CODE, INVALID_LINE_PATCH, UNKNOWN_LINE) несут в detail line_id и line_code первого неверного элемента; LINE_CODE_REQUIRED добавлен к четырём кодам запроса фронта |
экран ставит ошибку у поля нужной строки, не угадывая (план 6, O-2); одно правило для всех ошибок элемента | при нескольких неверных элементах фронт видит только первый — как и прежде |
| 2026-10-18 | R-1. Порядок исполнения части А: A0–A4, затем A6–A13; A5 (контракт 2.0, recognition_exchange.py) — последней, после слияния плана 0 (в origin/main нет api/tests/contract_identity.py) |
A5 зависит от теста идентичности плана 0, остальные задачи его результат не потребляют | возможные конфликты слияния в documents.py / recognition_exchange.py при A5 в конце |
| 2026-10-18 | R-2. Исполнители задач и их ревьюеры — sonnet; финальное ревью ветки — opus | план даёт прозу, а не готовый код; ревью ветки ловит сквозные расхождения | более слабая модель на задаче — лишние итерации ревью |
| 2026-10-18 | R-3. Тесты с БД идут на scratch-БД armcore_pytest_* compose-Postgres :5432 (устройство conftest); контейнеры не останавливаются и не пересоздаются |
общий стенд разработки не должен падать из-за прогонов | недоступный Postgres — задача BLOCKED, а не пропуск тестов |
| 2026-10-18 | R-4. A0 (только артефакты OpenSpec) без отдельного ревью сабагентом: достаточно openspec validate --strict |
кода в задаче нет | неточность формулировок дельт; поймало финальное ревью |
| 2026-10-18 | R-5. Ревью A1 и A2 объединено в один пакет после A2 | downgrade-тесты A1 проверяемы только с A2 | находки по A1 пришли на один раунд позже |
| 2026-10-18 | R-6. Ревью малых задач объединялось парами (A3+A4, A8+A9, A10+A11, A12+A13) | экономия раундов ревью | находки по первой задаче пары приходят на шаг позже |
| 2026-10-18 | R-7. Change unified-normalization-core-a архивируется только вместе с unified-normalization-core-b; тексты поведения части Б в дельтах А остаются |
разбор дельт на два change дороже и не нужен до выкладки обеих частей | если А заархивировать отдельно, openspec/specs опишет нереализованное |
| 2026-10-18 | R-8. Одна волна правок по итогам финального ревью (sonnet), затем один scoped re-review; M-6, M-8, M-13 не правятся (deferred / rejected) | замечания малы; M-13 (advisory-блокировки purge пофайлово) — окно обслуживания | M-6 (отдельный SUM для code_amount) и M-8 остаются техдолгом |
| 2026-10-18 | A1-1. result_mapping.map_file до A5/A9 присваивает file_ordinal порядковым номером создания документов |
иначе uq_document_stored_file_id_file_ordinal ломает файл с 2+ документами |
номера не равны индексу в documents[]; заменено в A9/A5 |
| 2026-10-18 | A1-2. organization_name_modified — nullable boolean (как в DBML); миграция пишет false |
соответствие DBML v5.19 | нет |
| 2026-10-18 | A1-3. Тест-фабрика tests/factories.py и tests/test_models_part_04_06._document нумеруют file_ordinal |
уникальность (stored_file_id, file_ordinal) |
нет |
| 2026-10-18 | A1-4. Старый тест «две BALANCE с NULL -> IntegrityError» переписан под DISCLOSURE | по v5.19 для форм это больше не нарушение | нет |
| 2026-10-18 | A1-5. Имена FK/PK новых объектов (fk_financial_statement_value_confirmed_by_app_user, fk_document_issue_*, pk_document_issue) — по NAMING_CONVENTION |
совпадают с create_all, тест схемы зелёный |
нет |
| 2026-10-18 | A2-1. line_status считается потерянным при <> 'ok' (колонка NOT NULL default 'ok') |
IS NOT NULL всегда истинно |
шум в журнале отката |
| 2026-10-18 | A2-2. Булевы счётчики и organization_name_modified считаются как IS TRUE; причина отказа «наименование правилось» — organization_name_modified IS TRUE |
NULL не считается правкой | нет |
| 2026-10-18 | A2-3. Причина «строки формы без кода» — по line_code IS NULL (любые), «повтор» — только по ненулевым кодам |
точность причин отказа отката | нет |
| 2026-10-18 | A2-4. Сообщение журнала склеивает счётчики через ; |
читаемость | нет |
| 2026-10-18 | A3-1. Только PATCH, меняющий crm_id, пишет GROUP_CHANGE (before/after всех изменённых полей запроса); PATCH только имени/даты остаётся MEMBERSHIP_CHANGE; создание — MEMBERSHIP_CHANGE с crm_id в payload |
P3-18 / Р-41 про смену crm_id; минимум изменения поведения |
фильтр аудита по действию для правок групп расходится; правка в одну строку |
| 2026-10-18 | A3-2. Предпроверка уникальности crm_id не учитывает видимость (глобальная уникальность), как для ГК |
единый подход | crm_id невидимой группы раскрывает существование через 409 (как ГК/ЮЛ) |
| 2026-10-18 | A3-3. Before-payload удаления группы включает crm_id |
полнота аудита | нет |
| 2026-10-18 | A4-1. run принимает подменяемую фабрику транзакции (по умолчанию session_transaction) |
тесты идут на db_session |
нет (боевой путь — по умолчанию) |
| 2026-10-18 | A4-2. PurgePlan несёт include_mixed; BlockingVersion по ex_snapshot_item имеет статус EX_SNAPSHOT_ITEM и version_id = ex_snapshot.source_version_id |
сигнатура apply_purge без флага |
нет |
| 2026-10-18 | A4-3. Проверка блокировки покрывает ВСЕ документы удаляемых целиком файлов (в том числе OSV/DISCLOSURE; OSV смешанных файлов с --include-mixed) |
иначе удаление сломало бы FK/заморозку | лишний отказ, если OSV удаляемого файла в верифицированной версии (корректно: она заморожена) |
| 2026-10-18 | A4-4. apply_purge блокирует все файлы плана (jobs -> file -> documents, по id файла), перепланирует под блокировками и отказывает до первой мутации (PurgeRefusedError); версии блокируются позже (file -> doc -> version) |
защита от гонки с загрузкой | окно между проверкой и блокировкой версий (T4.3, rejected) |
| 2026-10-18 | A4-5. Dry-run с блокировкой печатает блок отказа, но выходит с 0; --apply с блокировкой: отчёт в stdout, сообщение в stderr, код 1 |
P3-13: 0 = отчёт | нет |
| 2026-10-18 | A4-6. FILE_DELETE.after_value: для целого файла {unbound_version_ids}, для смешанного {deleted_documents, unbound_version_ids} |
трассируемость очистки | нет |
| 2026-10-18 | A4-7. superseded_with_osv — файлы, у которых superseded_by_file_id указывает на удаляемый файл и которые содержат OSV |
сохранение замещения ОСВ | нет |
| 2026-10-18 | A6-1. section_number возвращает строчные буквы (работает по нормализованному тексту) |
внутренний формат | нет |
| 2026-10-18 | A6-2. names() — имена без повторов в порядке редакций и появления; edition_codes — по всем записям |
стабильность подсказки | нет |
| 2026-10-18 | A6-3. Если «итого по разделу» только у одного из имён, номер у другого None — сравнение только по равенству строк |
спека | нет |
| 2026-10-18 | A6-4. Пустые справочные имена пропускаются | шум | нет |
| 2026-10-18 | A5-1. FileWarning наследует DocumentWarning (есть также line_refs/column_index/field): схема fileWarning = warning + locator |
надмножество брифа, без дублирования полей | нет |
| 2026-10-18 | A5-2. MetricsV2 наследует Metrics (переопределяет warnings); ветвление в _metrics_json идёт по классу конверта, не метрик |
изоляция v1/v2 при общей базе метрик | v2-конверт обернул бы уже готовые структуры; покрыто test_inbox_v2_warnings_kept |
| 2026-10-18 | A5-3. parse_message/parse_result: не-2 MAJOR отдаётся модели v1, чей шаблон ^1\.\d+$ даёт ValidationError (3.0, «banana», нет поля); нечитаемый JSON в parse_result — json.JSONDecodeError (ValueError), не ValidationError |
единая точка отказа для неподдерживаемых версий | вызывающий код, ждущий только ValidationError, пропустит ошибку чтения JSON (см. открытый вопрос) |
| 2026-10-18 | A5-4. У v2-предупреждения без locator в метрики пишется как пришло (ключа нет), у LEGACY — locator: None |
не домысливать данные, не менять пришедшее | потребитель обязан читать locator как необязательный (file_warnings так и делает) |
| 2026-10-18 | A5-5. RecognitionResultMessageV2 не наследует v1; общие поля вынесены в _MessageBase |
изоляция isinstance между версиями |
нет |
| 2026-10-18 | A5-6. file_warnings: locator не-строка/не-null -> объект пропускается |
защита от мусора во входящем конверте | потеря предупреждения с некорректным locator |
| 2026-10-18 | A6-5. Старые тесты API обновлены под код 1105 в fsbu4 (yaml дополнен 1105/1130/1140/1530/2460 и вторыми именами) |
данные docs | для ГСК-кода 1105 на 66н с датой fsbu4 появляется пустая строка «Гудвил» (O-2 A6) |
| 2026-10-18 | A7-1. FailedCheck.codes — поле с default (), участвует в сравнении |
P3-14 | низкая |
| 2026-10-18 | A7-2. insert_statement до маппера 2.0 пишет row_index = порядковый номер строки черновика |
иначе все строки источника стали бы origin = template |
заменено в A9/части Б |
| 2026-10-18 | A7-3. checks_input принимает объекты-колонки (id, column_index) — протокол, без импорта моделей в домен |
чистый домен | нет |
| 2026-10-18 | A7-4. code_amount: колонка — первая по column_index, затем SUM по ней; нет колонки или непустых — None |
определённость суммы | M-6: отдельный SUM, deferred в часть Б |
| 2026-10-18 | A7-5. В API checks.failed[].codes и line_ids не выведены в A7 (только домен) |
не в брифе A7 | закрыто в A8 |
| 2026-10-18 | A7-6. Порядок строк ответа в A7 не менялся | P3-17 — задача A8 | нет |
| 2026-10-18 | A8-1. insert_statement временно пишет period_kind (absolute при дате, иначе none) |
иначе тест брифа на 1.x не проходит до A9 | заменено в A9; совпадает с backfill миграции |
| 2026-10-18 | A8-2. FailedCheckOut.line_ids — объединение line_ids агрегата по codes правила; у межформенной проверки — строки только читаемой формы |
кода второй формы в агрегате нет | фронт не подсветит строку другой формы |
| 2026-10-18 | A8-3. header.source: запись с text: null отдаётся как есть, нечисловая уверенность -> null, не-dict записи пропускаются |
T1.1: «нет подсказки» | нет |
| 2026-10-18 | A8-4. order_lines: detail с неизвестным родителем ведёт себя как detail без родителя; вложения по row_index; равные ключи — порядок входа (по id) |
P3-17 | нет |
| 2026-10-18 | A8-5. IssueCounts — dataclass репозитория, сервис переводит в IssueCountsOut |
слои | нет |
| 2026-10-18 | A9-1. ColumnDraft вместо кортежа в StatementDraft.columns (несёт period_kind / label_confidence) |
нужны поля колонки | правка 4 утверждений в тестах |
| 2026-10-18 | A9-2. header_source.*.confidence в БД — число как пришло из 1.x (float), не Decimal |
JSONB; P3-20 | нет |
| 2026-10-18 | A9-3. Повтор формы определяется только при известных годе и квартале у обеих; текст issue без префикса locator, в metrics.mapping_warnings — с префиксом |
P3-4 | нет |
| 2026-10-18 | A9-4. Пустая ячейка (нет значения) -> needs_review = false (value_clean(None, None) истинно) |
формула приёма 1.x | нет |
| 2026-10-18 | A9-5. У листа ОСВ issue пишутся и для предупреждения о повторе счёта (относится к первому листу) | P3-5 | нет |
| 2026-10-18 | A10-1. Контрагент UNKNOWN трактуется как None, блокирует только MISMATCH |
спека: counterparty_check != MISMATCH |
низкая |
| 2026-10-18 | A10-2. name_mismatches: строка ok с line_code_confidence = None считается уверенной (не проверяется); line_status сравнивается со StatementLineStatus.ok (StrEnum) |
спека: «confidence < 100» (T10.1: покрыто комментарием и тестом) | строка без уверенности не даёт NAME_MISMATCH |
| 2026-10-18 | A10-3. has_error_warning: строго severity == "error" (регистр важен), не список -> False |
строгий контракт | нет |
| 2026-10-18 | A10-4. noqa PLR0913 на form_status_v2 и тестовом хелпере |
сигнатура задана планом | нет |
| 2026-10-18 | A11-1. Тест test_disclosure_is_confirmable удалён: form_blocker принимает meta, у расшифровки её нет; не-форма проверяется вызывающими (FORM_TYPES) — тест возвращён в волне правок (T11.1) на уровне сервиса |
покрытие подтверждения DISCLOSURE/OTHER | нет |
| 2026-10-18 | A11-2. confirmed_twin при пустом периоде возвращает None (PERIOD_REQUIRED сработает раньше); первым выбирается близнец с меньшим file_ordinal |
детерминизм | нет |
| 2026-10-18 | A11-3. Дубль ищется только среди CONFIRMED; второй NEEDS_CHECK-близнец не блокирует |
бриф | нет |
| 2026-10-18 | A11-4. line_id в extra — строка (str(uuid)), как document_id |
единообразие | нет |
| 2026-10-18 | A12-1. Новый код 422 DUPLICATE_COLUMN (колонка дважды в columns[]) |
в брифе не было | низкая |
| 2026-10-18 | A12-2. Подтверждение даты отчёта требует непустого report_date (null == null не подтверждение) |
смысл подтверждения | низкая |
| 2026-10-18 | A12-3. Равное текущему organization_name — no-op |
подтверждать нечего | нет |
| 2026-10-18 | A12-4. Снятие подтверждения ячейки и возврат суммы к исходной: needs_review = не(правлена |
подтверждена | строка detail |
| 2026-10-18 | A12-5. recompute дополнительно сбрасывает needs_review у правленых/подтверждённых значений и значений строк detail (settle_edited_values) |
согласованность флагов | T13.3: флаг навсегда false у значений detail (часть Б) |
| 2026-10-18 | A12-6. Подтверждение ячейки без записи значения в БД — 422 UNKNOWN_CELL до записей; колонка не этой формы в values[] — UNKNOWN_CELL, UNKNOWN_COLUMN — только для columns[] |
существующий тест | нет |
| 2026-10-18 | A12-7. is_current пересчитывается только при смене дат колонок (самая поздняя дата, меньший column_index) |
как при приёме | нет |
| 2026-10-18 | A12-8. Ключи аудита подтверждений: value:<id>:confirmed, column:<id>:date_confirmed, needs_review_fields; смена даты колонки — column:<id> |
единый формат | нет |
| 2026-10-18 | A12-9. Смена report_date убирает report_date из needs_review_fields без отдельного ключа аудита |
минимум шума | нет |
| 2026-10-18 | A12-10. lines[] в StatementPatch нет в A12 (добавлено в A13) |
разбиение задач | нет |
| 2026-10-18 | A13-1. Тесты используют код 1215 вместо 1250 из брифа | 1250 есть в источнике ББ Межениновской, а нужен «только шаблонный» код | нет |
| 2026-10-18 | A13-2. tests/test_api_statements.py::insert_line кладёт StatementLineStatus, а не строку |
identity map сервиса | нет |
| 2026-10-18 | A13-3. Пометка шаблонной строки (row_index IS NULL) как detail — 422 INVALID_LINE_PATCH; подтверждение кода у строки не ok — тоже |
спека такого перехода не перечисляет; у detail нужен row_index для родителя |
низкая, фронт такого не шлёт |
| 2026-10-18 | A13-4. Назначение того же кода строке ok с этим кодом — no-op; detail -> detail — no-op |
нечего менять, аудита нет | T13.1: no-op элемент может дать ложный 409 — недостижимо |
| 2026-10-18 | A13-5. Порядок проверок по элементам: действие, повтор, UNKNOWN_LINE, LINE_CODE_REQUIRED, INVALID_LINE_CODE, допустимость; затем отдельным проходом LINE_CODE_TAKEN (409 после всех 422) |
предсказуемость | нет |
| 2026-10-18 | A13-6. needs_review при снятии подтверждения: ok и не line_code_modified и уверенность < 100 |
правило приёма 1.x; для 2.0 уточнит часть Б | нет |
| 2026-10-18 | A13-7. Назначение кода: confidence = Decimal("100.00") (ответ отдаёт объект сессии) |
без перечитывания | нет |
| 2026-10-18 | A13-8. Удаление пустой шаблонной строки пропускается, если она сама — цель элемента запроса | избежать удаления цели | нет |
| 2026-10-18 | A13-9. Отступление от P3-10: 422 в values / columns после применённых lines откатывает всю транзакцию запроса; отдельной предпроверки адресации values для lines нет. UNKNOWN_CELL по line_code видит назначения lines этого запроса (они применены и flush-нуты раньше); 409 приоритетнее такого 422 |
эквивалентное наблюдаемое поведение при меньшем коде (T13.5) | при ошибке в откате частичная запись — тест M-10 фиксирует отсутствие записей и аудита |
| 2026-10-18 | A13-10. user_id в apply_line_edits принят по интерфейсу, не используется; аудит строк — ключ line:<id> с {line_code, line_status, parent_row_index, is_confirmed}, в него попадают и detail со сменой только родителя |
у строки нет автора правки | нет |
| 2026-10-18 | A13-11. Пометка шаблонной строки как detail = 422 INVALID_LINE_PATCH |
см. A13-3 | низкая |
Open questions
Вопросы владельцу (O-n из плана 3, не закрытые решениями Р-34 / Р-35 / Р-40 / Р-41; нумерация плана).
- O-1. Р-10 для
1.x(P3-4): вторая форма того же типа хранится и при1.x(план) или пропускается, как раньше? - O-3 (закрыт Р-40 для четырёхзначных).
CODE_NOT_IN_DICTIONARY/CODE_NOT_IN_EDITION— только для четырёхзначных кодов; пятизначные подстроки Excelinfoне дают. Подтвердить, что сценарий спеки «Нестандартный код Excel» приведён к этому. - O-4. Пересчёт проверок ядра уровня
infoпосле правки (P3-8) спека не описывает; после смены кода они не пересчитываются в части А (T13.4), пересчёт — в части Б. - O-6 (закрыт Р-41). Смена
crm_idГСК/ГСЗ —GROUP_CHANGE. - O-7. Конверт с неизвестным MAJOR (
3.0) inbox отбрасывает с предупреждением в лог (задание закроется по сроку); сценарий «файл получаетERRORсmapping_error» выполняется для файла результата. Оставить? - O-9. Выкладка: PR части А в
mainбез снимка контура до части Б; встроенный фронтarm-core/webполучает новые типы только черезsync-webпосле плана 6. - O-10. Метрики
fields_*ядро не считает по строкам формы (копирует из конверта,recognition_exchange.FIELD_COUNTERS). Подтвердить, что спека («метрикиfields_*») не требует нового расчёта. - Наблюдение. Хранимая вторая ББ теперь входит в ключи автозамещения: файл с ББ на 30.09.2025 и ББ на 31.12.2024 может заместить более старый файл; его ОФР при этом выпадает с предупреждением OQ-154.
- A5-O1.
parse_resultна битом JSON бросаетJSONDecodeError/UnicodeDecodeError, а неValidationError: в части Б при переключении_load_resultловитьValueError. - A5-O2.
statement-code-orders.yamlне скопирован вapi/contracts(тест идентичности его не покрывает); при необходимости ядру — скопировать в части Б. - A5-O3.
frontend-api-guideв docs должен описыватьJobBrief.warningsкак объекты (docs, план 6). - Нумерация. Пропуски в нумерации O-n (O-2, O-5, O-8) закрыты планом/рулингами Р-n.