# Диалог DeepSeek ↔ Opus — Decoupling пайплайна Дата: 28.06.2026 --- ## Запрос DeepSeek (задание) Файл: `History/opus-decoupling-plan-2026-06-28.md` 5 блоков, интерактивный режим: 1. Модульность пайплайна — контракты между стадиями 2. Интеграционное тестирование (fixture-based) 3. Деплой и синхронизация 4. Перспектива переезда на managed Flask 5. Вопросы к заказчику --- ## Ответ Opus — 5 развилок ### Вопрос 1. Граница decoupling Варианты: - A. Единый интерфейс пайплайна (контракт оркестратора) - B. Изолировать только внешние зависимости (LLM, БД) - C. И то, и другое **Рекомендация Opus:** C ### Вопрос 2. База данных в тестах Варианты: - A. Абстрагировать репозиторий за интерфейсом → in-memory заглушка - B. Реальный Postgres в Docker/testcontainers - C. Гибрид: юнит-тесты на заглушке + интеграционные на Postgres **Рекомендация Opus:** C ### Вопрос 3. Мокирование LLM Варианты: - A. DI — LLM-клиент через зависимость - B. Мокать на уровне httpx-транспорта - C. Record/replay — реальные ответы как фикстуры **Рекомендация Opus:** A + C ### Вопрос 4. Тестовый фреймворк Варианты: - A. pytest + папка tests/ + conftest - B. Только smoke-тесты - C. Полное покрытие **Рекомендация Opus:** A → C поэтапно ### Вопрос 5. Деплой Варианты: - A. Не трогать, отдельная задача - B. Заодно привести sync.sh/systemd - C. Только задокументировать **Рекомендация Opus:** A --- ## Ответ DeepSeek **Все 5 рекомендаций — согласен:** - 1C, 2C, 3A+C, 4A, 5A Запрос: жду финальный план. --- ## Ответ Opus — Финальный план (5 фаз) ### Фаза 1 — Контракты стадий (1C) - `deploy/contracts.py` с dataclass'ами: ParseResult, ClassifyResult, GroupingResult, CompareOp - Разделить стадии на pure-core + HTTP-adapter - `handle_upload` → `parse_multipart` + `store_document` - `parse_file` уже pure (эталон) - `classify_batch`, `group_documents` принимают `repo` параметром ### Фаза 2 — Изоляция LLM (3A+C) - `deploy/services/llm_client.py`: протокол `LLMClient.complete(prompt) -> str` + `HttpxLLMClient` - `call_llm` / `_call_llm_classify` принимают `llm_client` (default `HttpxLLMClient()`) - `FakeLLMClient` для тестов — сохранённые реальные ответы - Зависит от Фазы 1 ### Фаза 3 — Изоляция БД (2C) - Тонкий фасад `Repository` (Protocol) поверх `db/*.py` - Без переписывания SQL - `MemRepository` для юнитов, `PgRepository` для интеграции - Docker `postgres:15`, очистка TRUNCATE в teardown - Параллельно с Фазой 2 ### Фаза 4 — pytest (4A→C) - Структура: `deploy/tests/` → `conftest.py`, `fixtures/`, `unit/`, `integration/` - Фикстуры: `fake_llm`, `mem_repo`, `pg_repo`, `sample_docx` - Порядок: smoke per-stage → полное покрытие → сквозной прогон - Зависит от Фаз 1-3 ### Фаза 5 — Деплой (5A) - Не трогаем - Флаг `UPLOAD_BACKEND=vm|managed` — только интерфейс ### Ключевые файлы - upload.py, classify.py, grouping.py, process.py, llm.py, connection.py — модифицируются - Новые: contracts.py, llm_client.py, repository.py, tests/, docker-compose.test.yml ### Верификация 1. `pytest unit/` — зелёные без сети/БД 2. `docker-compose up postgres` + `pytest integration/` — реальный SQL 3. Сквозной: sample.docx → пайплайн → ожидаемые ops 4. convert_server.py без изменения поведения 5. JS-тесты (131) — PASS ### Scope Включено: контракты, DI LLM+БД, pytest, фикстуры, флаг UPLOAD_BACKEND Исключено: деплой, переписывание SQL, изменения фронтенда, managed-upload --- ## Ответ Opus — Финальные уточнения (3 вопроса) ### 1. Dataclass vs TypedDict **Решение:** микс. Вход — dict (из БД/JSON), выход — frozen dataclass. Конвертация dict→dataclass через классметод `from_llm(raw: dict)` на границе стадии. ### 2. Сигнатура LLMClient **Решение:** один метод `complete(prompt: str) -> str`. Парсинг JSON и `prompt_id` — НЕ дело LLM-клиента, остаются у вызывающего. ### 3. Уровень conftest.py **Решение:** `deploy/conftest.py`. Корень Python-кода, чистые импорты. ### Поправка по upload `handle_upload` уже читает в `BytesIO`. `parse_multipart(raw_bytes)` всё равно выделяем. --- ## Итоговый план (зафиксирован) Фазы: **Ф1 → (Ф2 ∥ Ф3) → Ф4**, Ф5 отдельно. Развилки: 1C, 2C, 3A+C, 4A, 5A. План принят DeepSeek. Готов к исполнению — Фаза 1.