From b1da9b8774c5e57268742e3eed9e664b2f7588e8 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Tue, 6 Oct 2026 22:48:40 +0400 Subject: [PATCH] =?UTF-8?q?plan:=20=D0=A1=D1=80=D0=B5=D0=B7=203a=20?= =?UTF-8?q?=E2=80=94=20Task=200=20=D0=B2=D1=8B=D0=BF=D0=BE=D0=BB=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20(main=20=D1=81=20v1.2=20=D0=B2=D0=BB=D0=B8?= =?UTF-8?q?=D1=82,=20749=20passed),=20OQ=20=D1=81=20144,=20CONTRACT=5FVERS?= =?UTF-8?q?ION=201.0=20(=D1=80=D0=B5=D1=88=D0=B5=D0=BD=D0=B8=D0=B5=20?= =?UTF-8?q?=E2=84=9614)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_012vJkmBGKKqMoEMzvsXG3y2 --- .../2026-10-06-slice-3a-ocr-normalizer.md | 43 ++++++++----------- .../changes/slice-3a-ocr-normalizer/design.md | 1 + 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/docs/superpowers/plans/2026-10-06-slice-3a-ocr-normalizer.md b/docs/superpowers/plans/2026-10-06-slice-3a-ocr-normalizer.md index 1ac840b..83bfdc5 100644 --- a/docs/superpowers/plans/2026-10-06-slice-3a-ocr-normalizer.md +++ b/docs/superpowers/plans/2026-10-06-slice-3a-ocr-normalizer.md @@ -23,9 +23,11 @@ - Репозиторий scan-gateway, ветка `feat/6-slice-3a-ocr-normalizer`, worktree `.wt/slice-3a/scan-gateway`; команды `make` — из корня репозитория, `pytest` — из `api/` (`cd api && .venv/bin/pytest …`). Перед первой задачей — `make venv`. -- Ветка должна содержать контракт v1.2: Task 0 вливает `origin/chore/contract-v1-2` (scan-gateway PR #5). Контракт - **не меняется**: `api/contracts/*.schema.json` остаются байт в байт равными `../docs/contracts` (тест `test_contract.py`). - Если `../docs/contracts` ещё v1.0 (docs #27 не слит), тест идентичности упадёт — это ожидаемо до мержа docs #27; задача 17 проверяет. +- Ветка уже содержит контракт v1.2: 06.10.2026 слиты docs #27/#44, scan-gateway #5, arm-core #27, tabular-parser #16/#19, + fixtures #10; `origin/main` влит в ветку (merge 3bc01e5), `make check` — 749 passed. Контракт **не меняется**: + `api/contracts/*.schema.json` байт в байт равны `../docs/contracts` (тест `test_contract.py`). +- `CONTRACT_VERSION` scan-gateway остаётся `"1.0"` (решение №14 `design.md`): документы 3a не используют полей, + появившихся в 1.1–1.2 (`DISCLOSURE`, `checks`, границы периода ОСВ), а потребитель (arm-core #27) принимает любой MINOR. - Язык комментариев, docstring'ов, сообщений и коммитов — русский; идентификаторы — английские. Строка ≤ 120 символов (`ruff`, правила `E, F, I, UP, B, PL`); `mypy src` с `check_untyped_defs`. `make check` зелёный после каждой задачи. - Функции `src/normalizer/ocr/*` — чистые: без I/O, без `get_settings()`, без логирования внутри конвейера (кроме @@ -100,23 +102,11 @@ - Produces: модели `src.contract.result`: `StatementDocument` с `document_type ∈ {BALANCE, INCOME, DISCLOSURE, OTHER}`, `checks: StatementChecks | None`; `StatementValue.string_value: str | None`; `StatementChecks`, `FailedRelation`. -- [ ] **Step 1: Влить копию схемы v1.2** +- [x] **Step 1: Влить main с копией схемы v1.2** — выполнено 06.10.2026 при подготовке плана: `git merge origin/main` + (merge 3bc01e5, PR #5 уже в main). -```bash -cd /Users/andrey.epifancev/projects/awp-ca/.wt/slice-3a/scan-gateway -git fetch origin -git merge --no-edit origin/chore/contract-v1-2 -``` -Ожидание: merge без конфликтов (ветка #5 меняет только `api/contracts/`, `api/src/contract/result.py`, `tests/unit/test_contract.py`, README). - -- [ ] **Step 2: Окружение и базовая проверка** - -```bash -make venv -make check -``` -Ожидание: `ruff`/`mypy` чисты; pytest зелёный, кроме, возможно, `tests/unit/test_contract.py::test_schemas_identical_to_docs`, -если `../docs/contracts` ещё v1.0 (docs #27 не слит) — тогда зафиксировать это в отчёте задачи и продолжать. +- [x] **Step 2: Окружение и базовая проверка** — `make venv`, `make check`: линт чист, 749 passed. Исполнитель повторяет + `make check` перед Task 1, чтобы убедиться, что окружение живо. - [ ] **Step 3: Проверить, что модель видит v1.2** @@ -125,7 +115,7 @@ cd api && .venv/bin/python -c "from src.contract.result import StatementValue, S ``` Ожидание: печать описания поля без ошибки импорта. -- [ ] **Step 4: Commit** — merge-коммит уже создан шагом 1; дополнительного коммита нет. +- [x] **Step 4: Commit** — merge-коммит 3bc01e5; дополнительного коммита нет. --- @@ -285,7 +275,8 @@ Expected: `ImportError: cannot import name 'load_rules'`. # Редакция выбирается по дате отчёта документа: valid_until / valid_from — включительно. # 66n — Приказ Минфина № 66н (отчётность до 2024 года включительно), источник — codes_mapping старого ядра DKB_ARMKA # (проверен на стенде ИС Сканирование). fsbu4 — ФСБУ 4/2023 (отчётность за 2025 год и позже): заполняет владелец -# по Приложениям 3–4 ФСБУ 4/2023; пока пуста, документы 2025 года получают имена из OCR и предупреждение. +# по Приложениям 3–4 ФСБУ 4/2023 (черновой перечень кодов — шаблоны fixtures/statements-xlsx/templates/*после 01.01.2025*.xlsx); +# пока пуста, документы 2025 года получают имена из OCR и предупреждение. editions: - id: 66n valid_until: 2024-12-31 @@ -3012,6 +3003,7 @@ class TestSlice3a: ``` (`API_DIR`, `BUCKET`, `emulator`, `EnvFactory`, `json`, `pytest` — импорты, уже используемые в модуле; `API_DIR = Path(__file__).resolve().parents[2]` при отсутствии.) +`CONTRACT_VERSION` и проверка `schema_version == "1.0"` в `tests/worker_helpers.py` **не меняются** (решение №14). В `api/tests/unit/test_app.py` добавить: @@ -3098,8 +3090,7 @@ Docstring модуля: пункт про очистку и ссылку на D- Docstring `app.py`: `Worker с OcrNormalizer`, справочники из `SCAN_RULES_DIR`. - [ ] **Step 5: Тесты и линт** — `cd api && .venv/bin/pytest tests/unit -q -k "not emulator" && cd .. && make lint` → PASS -(`test_emulator.py` чинит Task 14). Если `published()` в `worker_helpers` проверяет `schema_version == "1.0"` конверта, а -`CONTRACT_VERSION` после мержа #5 стал `1.2`, поправить помощник на `CONTRACT_VERSION` из `src.contract`. +(`test_emulator.py` чинит Task 14). - [ ] **Step 6: Commit** — `git add api/src/core/worker.py api/src/app.py api/tests/unit/conftest.py api/tests/unit/test_worker.py api/tests/unit/test_worker_concurrency.py api/tests/unit/test_app.py`, сообщение `feat(worker): очистка горячей папки под SCAN_CLEANUP, RECOGNITION_FAILED без расширения, OcrNormalizer в lifespan (D-2, D-4)`. @@ -3336,9 +3327,9 @@ touch /Users/andrey.epifancev/projects/awp-ca/.wt/slice-3a/docs/.bounded предупреждение `BACKEND_PARSING_ERROR` уровня таблицы, остальные таблицы разбираются; ни одной таблицы → `error RECOGNITION_FAILED`». Добавить п. 10 «Редакции форм»: выбор по дате отчёта → поздней `period_date` → совпадениям кодов; пустая редакция → имена из OCR. 3. `07-open-questions.md`: OQ-137 — дописать «уточнение 06.10.2026: `Tmp` остаётся, очистка под `SCAN_CLEANUP`»; новые строки - OQ-138…OQ-141 (проверить последний номер `grep -o "OQ-[0-9]*" | sort -V | tail -1` — в ветке 2b уже занят OQ-138, брать следующие - свободные после мержа docs #27): права на удаление на SMB-шаре контура; редакция формы в контракте (MINOR 1.3); словари §7/§12 - по редакциям для tabular-parser (2b) и ядра (3b), расширение OQ-12; скобки, распознанные как `}`/`]` (решение №12 спеки 3a). + OQ-144…OQ-147 (на 06.10.2026 последний — OQ-143 из 2b; перед правкой проверить последний номер в таблице): + права на удаление на SMB-шаре контура; редакция формы в контракте (MINOR 1.3); словари §7/§12 по редакциям для + tabular-parser и ядра (3b), расширение OQ-12 и OQ-139; скобки, распознанные как `}`/`]` (решение №12 спеки 3a). 4. `09-delivery-plan.md`: Срез 3 → подпункты 3a (scan-gateway, change `slice-3a-ocr-normalizer`) и 3b (arm-core). 5. `dev/docker-compose.yml`: `scan-gateway` → `SCAN_CLEANUP: "true"`; `scan-emulator` → `SCAN_EMULATOR_DEFAULT: ""` с комментарием; комментарий про `UUID_<имя>.ext` заменить на `.` и пары по sha256. `dev/.env.example`: при наличии `SCAN_*` — те же переменные. diff --git a/openspec/changes/slice-3a-ocr-normalizer/design.md b/openspec/changes/slice-3a-ocr-normalizer/design.md index bb33d46..4d48320 100644 --- a/openspec/changes/slice-3a-ocr-normalizer/design.md +++ b/openspec/changes/slice-3a-ocr-normalizer/design.md @@ -152,4 +152,5 @@ docs: bounded-PR (`03-architecture.md`, OQ-137, §11.1 п. 6 и 9, новые OQ | 10 | 2026-10-06 | `label` колонки — сырой текст шапки с `\n` → пробел; `period_date` из `parse_date` | ядро показывает текст как есть | — | | 12 | 2026-10-06 | `parse_amount` переносится дословно: скобка, распознанная как `}` (`(7744536}`), минус не даёт — сумма положительная с `is_recognized = false` | проверенный код; расширение множества скобок — отдельное правило §11.1 после проверки на стенде | знак потерян, аналитик видит `raw/` и подсветку | | 13 | 2026-10-06 | В переносе `parse_amount` признак `verified` считается до постановки знака минус (в источнике — после, и любая сумма в скобках получала `verified = False`) | иначе каждая отрицательная сумма формы давала `is_recognized = false` и `needs_check` без причины; единственное отличие от источника, зафиксировано тестом `(6032409) → -6032409, verified` | расхождение с поведением старого ядра на стенде: там такие суммы подсвечивались, у нас — нет | +| 14 | 2026-10-06 | `CONTRACT_VERSION` scan-gateway остаётся `1.0`, хотя копия схемы — v1.2 | документы 3a не используют полей 1.1–1.2 (`DISCLOSURE`, `checks`, границы периода ОСВ); потребитель принимает любой MINOR; меньше изменений в тестах конверта | если ядро начнёт различать MINOR производителя — одна константа и проверка в `worker_helpers` | | 11 | 2026-10-06 | Имя строки: из словаря редакции по коду; из OCR — только если кода нет в словаре | старое ядро, §11.1 п. 6 | расхождение имени словаря и формы клиента не видно аналитику (видно в `raw/`) |