# Сессия 2026-06-28 — Итоги ## Decoupling пайплайна (Ф1-Ф4) ### Ф1 — Контракты + upload decouple - `deploy/contracts.py` — 5 dataclass'ов: ParseResult, ClassifyResult, GroupingResult, BatchGroupingResult, CompareOp - `deploy/services/upload.py` — выделены `parse_multipart()` и `store_document()` - `handle_upload` теперь тонкий HTTP-handler ### Ф2 — LLM Client DI - `deploy/services/llm_client.py` — протокол `LLMClient.complete(prompt)->str` + `HttpxLLMClient` + `FakeLLMClient` - `deploy/services/llm.py` — `call_llm(..., llm_client=None)` — DI с обратной совместимостью - `deploy/services/classify.py` — `_call_llm_classify(..., llm_client=None)` + `classify_batch(..., llm_client=None, repo=None)` ### Ф3 — Repository facade - `deploy/repository.py` — Protocol `Repository`, `PgRepository`, `MemRepository` (17 методов) ### Ф4 — pytest - `deploy/conftest.py` — фикстуры: `fake_llm`, `mem_repo`, `sample_docx_bytes` - `deploy/tests/unit/test_contracts.py` — 13 тестов (contracts, garbage, multipart) - `deploy/tests/unit/test_llm_repo.py` — 9 тестов (FakeLLM, MemRepository) - `deploy/tests/unit/test_classify_batch.py` — 4 теста (garbage×2, LLM, no_pending) - **26/26 PASS** ### Баги (ревью Opus) - #1 Двойная сериализация JSON в `PgRepository.set_document_parsed` — исправлено - #2 `db_docs` → `_db` в `classify_batch` — исправлено (6 вызовов) - #3 `ParseResult.from_row` element_count=0 — исправлено - #4 `GroupingResult.unresolved` → `BatchGroupingResult` — исправлено - Protocol расширен: +7 методов - `build_classify_prompt`: try/except вокруг `db.prompts.get_active()` - `MemRepository.set_classification`: +`classify_raw`, `classify_input` --- ## Дедупликация + защита от careless user ### content_hash - `db/documents.py` — `insert()` принимает `content_hash`, `get_by_hash()` для проверки - `services/upload.py` — `store_document()` считает `sha256` и пропускает дубликаты - `convert_server.py` — ALTER TABLE `content_hash` ### Сортировка по doc_date - `services/process.py` — `supps.sort(key=lambda s: (doc_date, created_at))` ### date_start в name_hash - `db/spec_events.py` — `_hash(name, date_start)` — хеш включает период, разные периоды = разные строки ### ZIP-защита - `services/unzip.py` — лимит 500 файлов, 500 MB, проверка ratio (100x), санитизация имён (cp437→utf8, path-traversal) ### Статус «не распознан» - `services/upload.py` — supplement НЕ создаётся при ошибке парсинга, `warning` при пустом результате - `try/except: pass` → `logging.warning` ### UNRESOLVED вместо тихих no-op - `db/spec_events.py` — `ADD` без имени → UNRESOLVED, `UPDATE/DELETE` с пустым `target_hash` → UNRESOLVED, неизвестный `action` → UNRESOLVED - `_log_unresolved()` — общая функция ### Нормализация чисел и дат - `services/metrics.py` — `_to_decimal`: пробелы → удалить, `,` → `.` - `services/metrics.py` — `normalize_date()`: DD.MM.YYYY → YYYY-MM-DD - `db/spec_events.py` — `_hash` применяет `normalize_date` --- ## Архитектура check.kube5s.ru - ВМ: порт 8777, БД `contracts_check`, код `~/contracts-check/` - Nginx: SSL (letsencrypt), CORS, proxy headers - Managed Flask: `contracts-flask.git`, `site/` + `Dockerfile` - VM_API = `https://check.kube5s.ru` - Полная изоляция от прода (contracts.kube5s.ru) --- ## Файлы History - `architecture.md` — архитектура Flask-стека - `session-01-init-2026-06-27.md` — создание стека - `opus-decoupling-plan-2026-06-28.md` — задание Opus - `opus-decoupling-review-2026-06-28.md` — рецензия DeepSeek - `opus-dialog-2026-06-28.md` — полный диалог DeepSeek↔Opus - `opus-review-request-2026-06-28.md` — запрос ревью - `opus-review-2-request-2026-06-28.md` — повторное ревью - `opus-review-response-2026-06-28.md` — результат ревью - `opus-dedup-2026-06-28.md` — дедупликация + засады (15 пунктов)