# Тикеты реализации «Контракт-чек» (production refactor) > **Актуальная архитектура:** `ARCHITECTURE.md` (supersedes `IMPLEMENTATION_PLAN.md` для этапов 1+). > Состояние кода на момент синхронизации: реализованы `api/` (вкл. B2B-роуты, web-auth, passkeys, magic links), > `core/`, `worker_extract/`, `worker_prescreen/`, `worker_analyze/`, `worker_notify/`, `bot/`, `prototype/`; > 7 Dockerfile-ов в `srv/`; `docker-compose.yml` разводит профили `services` и `edge`; > миграции `0001_initial` … `0010_passkeys_magic_links`; unit- и интеграционные тесты зелёные. > DoD: `ruff check src tests`, `ruff format --check src tests`, `uv run ty check src`, `pytest` зелёные; > `.env.example` актуален. > > Статусы: `todo` / `in_progress` / `done` / `blocked`. --- ## Аудит реализации (текущее состояние) > Проверено: код собирается (`pyproject.toml` + `uv.lock`), миграции накатываются, > `api/` стартует, unit-тесты проходят (≈80 тестов зелёные), интеграционные тесты проходят на поднятой инфраструктуре. | Компонент | Статус | Доказательство / пробел | |-----------|--------|---------------------------| | Stage 0 prototype | done | `src/contract_check/prototype/` работает; `tests/unit/test_checklist_report.py`, `test_chunker.py`, `test_extractor.py` зелёные. | | `core/` — общий домен | done | `db/` (models, session, enums, repositories), `mq/`, `s3/`, `llm/`, `analysis/`, `extraction/`, `notifications/`, `security/`, `credits.py`, `tokens.py`, `api_keys.py`, `rate_limit.py`, `redis_client.py`, `config.py`, `logging.py`, `metrics.py`, `telemetry.py`, `sentry.py`, `passkeys.py`. | | Миграции / БД | done | `0001_initial.py` (6 таблиц) … `0010_passkeys_magic_links.py` (passkeys, magic links, user name). | | `api/` — FastAPI ядро | done | `POST /api/v1/documents`, `GET /api/v1/reports/{id}`, `GET /api/v1/me`, `/healthz`, `/readyz`, `/metrics`; user JWT auth (`/api/v1/auth/telegram/*`, `/api/v1/auth/{register,login,...}`, passkeys, magic links); B2B `/api/v1/analyze`; `/admin/*`; reserve-on-enqueue (`services.py`). | | `worker_extract/` | done | `consumer.py`/`handler.py`/`extract_document.py`/`__main__.py`: consume `DocumentUploaded` → MinIO dl → extract/OCR → `.txt` upload → publish `PrescreenRequested` (или `DocumentExtracted`, если прескрин выключен); failure-class + refund. | | `worker_prescreen/` | done | `consumer.py`/`handler.py`/`__main__.py` + `extractor*.py`/`router.py`/`config.py`: consume `PrescreenRequested` → гибридная экстракция метаданных → роутинг `deep_analysis`/`manual_review`/`auto_approve`; публикация `AnalyzeRequested` или терминальный статус/лёгкий отчёт. | | `worker_analyze/` | done | `consumer.py`/`handler.py`/`__main__.py`: consume `DocumentExtracted` → LLM → Report → `status=done`; refund-on-DLQ по политике. | | `worker_notify/` | done | `consumer.py`/`handler.py`/`__main__.py`: consume `NotificationMessage` → SMTP (password reset, magic link) или dev-лог при пустом `SMTP_HOST`. | | `bot/` — Telegram adapter | done | `client.py`/`config.py`/`handlers.py`/`__main__.py`: `/start`, upload→`POST /documents`, poll→deliver; граница импортов проверяется `tests/unit/test_bot_boundary.py`. | | Dockerfile-ы | done | `srv/{api,worker-extract,worker-prescreen,worker-analyze,worker-notify,bot,prototype}/Dockerfile` — все 7 (deps-группы PEP 735 заточены на сервис). | | Docker Compose | done | Инфра (default) + профиль `services` (api, 3 worker-а, bot) + профиль `edge` (nginx+certbot) с `depends_on: service_healthy`. Профиль `obs` — позже. | | Observability | in_progress | Prometheus-метрики (`/metrics`), Sentry, OpenTelemetry SDK — в коде. Полный стек Prom/Grafana/Tempo/OTel-collector — позже. | | Stage 3 — B2B API | done | `api/routes/b2b.py`, `core/api_keys.py`, `core/rate_limit.py`, `core/redis_client.py`, миграция `0002_api_keys.py`, `tests/integration/test_b2b_api.py`, `tests/unit/test_rate_limit.py`; `X-API-Key` auth + token-bucket rate-limit. | --- ## Этап 0 — Прототип (go/no-go) ### T-E0-001 — Аккаунт Ollama Cloud + каркас прототипа **Статус:** done · **Оценка:** S `.env.example` содержит `OLLAMA_HOST`/`OLLAMA_API_KEY`/`OLLAMA_MODEL=qwen2.5:14b`/`OLLAMA_FALLBACK_MODEL=qwen2.5:7b`. `python -m contract_check --help` работает. Теги без `-instruct`. ### T-E0-002 — Экстрактор текста (PDF/DOCX) **Статус:** done · **Оценка:** S `src/contract_check/core/analysis/extractor.py`; покрыт `tests/unit/test_extractor.py`. ### T-E0-003 — Чек-лист анализа (v1) **Статус:** done · **Оценка:** S `src/contract_check/core/analysis/checklist.py` — 10 пунктов с `id/title/description`. ### T-E0-004 — Ollama Cloud-клиент + repair-loop **Статус:** done · **Оценка:** M `src/contract_check/core/llm/ollama_cloud.py`; мок-тесты 200/429-fallback/битый-JSON в `tests/unit/test_llm_ollama_cloud.py`. ### T-E0-005 — Промпт + JSON-отчёт (с чанками) **Статус:** done · **Оценка:** M `core/analysis/analyzer.py`, `chunker.py`, `report_schema.py`; disclaimer в markdown. ### T-E0-006 — Замеры метрик и economics go/no-go **Статус:** todo · **Оценка:** M Требует живых прогонов 3–5 договоров через Ollama Cloud + `docs/stage0-results.md`. --- ## Этап 1 — Production refactor (api + core + workers + bot) > Архитектура: hexagonal, RabbitMQ pipeline (`extract.q` → `analyze.q`), MinIO, Postgres, Redis. > Профили Docker Compose: default = инфра; `services` = api + workers + bot. ### T-E1-001 — Ядро `contract_check.core` **Статус:** done · **Оценка:** L Пакет `src/contract_check/core/` содержит `db/`, `mq/`, `s3/`, `llm/`, `analysis/`, `credits.py`, `tokens.py`, `config.py`, `logging.py`, `metrics.py`, `telemetry.py`, `sentry.py`. ### T-E1-002 — Миграции БД (6 таблиц) **Статус:** done · **Оценка:** M `migrations/versions/0001_initial.py`: `users`, `documents`, `reports`, `jobs`, `service_tokens`, `invoices` (stub). ### T-E1-003 — FastAPI ядро: документы, кредиты, enqueue, отчёты **Статус:** done · **Оценка:** L `src/contract_check/api/`: `POST /api/v1/documents`, `GET /api/v1/reports/{id}`, `GET /api/v1/me`, `/healthz`, `/metrics`. Reserve-on-enqueue (`core/credits.py`) и user JWT auth (`core/auth.py`, `api/routes/auth/`). ### T-E1-004 — Worker-extract (CPU: pymupdf + tesseract) **Статус:** done · **Оценка:** M · **Зависимости:** T-E1-001, T-E1-002 `src/contract_check/worker_extract/` (`consumer.py`/`handler.py`/`extract_document.py`/`__main__.py`): consume `DocumentUploaded` из `extract.q` → скачать blob из MinIO → `extractor` + `ocr.ocr_pdf()` → загрузить `.txt` → publish `PrescreenRequested` в `prescreen.q` (или `DocumentExtracted` в `analyze.q`, если прескрин выключен). Failure-classification + refund-on-DLQ. Образ `srv/worker-extract/Dockerfile`. ### T-E1-005 — Worker-analyze (I/O: LLM provider) **Статус:** done · **Оценка:** M · **Зависимости:** T-E1-001, T-E1-002, T-E1-004 `src/contract_check/worker_analyze/` (`consumer.py`/`handler.py`/`__main__.py`): consume `DocumentExtracted` из `analyze.q` → скачать `.txt` → chunk → LLM (через `core/llm` port) → validate/repair → сохранить `Report`, `status=done`; refund при terminal failure (`core/credits.py`). Образ `srv/worker-analyze/Dockerfile`. ### T-E1-006 — Telegram-бот (aiogram 3) — HTTP-адаптер **Статус:** done · **Оценка:** M · **Зависимости:** T-E1-003 `src/contract_check/bot/` (`client.py`/`config.py`/`handlers.py`/`__main__.py`): `/start`, приём PDF/DOCX → `POST /api/v1/documents`, poll `GET /api/v1/reports/{id}` → отправка отчёта (разбивка/файл >4096 симв.), реакция на 402/400/202. Образ `srv/bot/Dockerfile`. **Не импортирует** `core.db`/`core.s3`/`core.llm`/`core.mq`/`core.credits` (проверяется `tests/unit/test_bot_boundary.py`). ### T-E1-007 — Docker Compose: сервисы и профили **Статус:** done · **Оценка:** M · **Зависимости:** T-E1-004, T-E1-005, T-E1-006 `docker-compose.yml`: default-профиль = инфра (`postgres`, `redis`, `rabbitmq`, `minio`, `minio-init`); профиль `services` = `api`, `worker-extract`, `worker-prescreen`, `worker-analyze`, `worker-notify`, `bot` с `depends_on: service_healthy`; профиль `edge` = nginx+certbot. Все 7 Dockerfile-ов в `srv/`. Профиль `obs` — позже (T-E1-010). ### T-E1-008 — Оплата (ЮKassa) и пополнение кредитов **Статус:** todo · **Оценка:** M · **Зависимости:** T-E1-003, T-E1-006 Роут для создания платежа 199 ₽ = 1 документ; webhook `succeeded` → `credits_left += 1`. ### T-E1-009 — Деплой + edge (Nginx/certbot) **Статус:** done · **Оценка:** M · **Зависимости:** T-E1-007 `deploy/nginx/templates/contract-check.conf.template`, `deploy/nginx/certbot-init.sh`, `DEPLOY.md §13`, `docker-compose.yml` профиль `edge`. ### T-E1-010 — Мониторинг (Prometheus/Grafana/Tempo/Sentry) **Статус:** todo · **Оценка:** M · **Зависимости:** T-E1-007 `deploy/observability/` + compose профиль `obs`. Дашборды: queue depth, job latency, LLM tokens, credits. --- ## Этап 2 — Веб + подписки ### T-E2-001 — Миграция БД: планы и счета **Статус:** todo · **Оценка:** M · **Зависимости:** T-E1-002 `users.plan` (`free|solo|team`), `users.plan_renews_at`. Таблица `invoices` уже существует (stub); наполнить логикой. ### T-E2-002 — Telegram Login Widget auth **Статус:** done · **Оценка:** M · **Зависимости:** T-E1-003 Реализовано в `core/auth.py` + `api/routes/auth/telegram.py`: `/api/v1/auth/telegram/web` и `/api/v1/auth/telegram/miniapp` проверяют HMAC-подпись Telegram и выдают тот же user JWT, что и бот. ### T-E2-003 — React SPA (Vite) **Статус:** todo · **Оценка:** L · **Зависимости:** T-E2-002 Загрузка, история, профиль, подписка. Сборка в статику; `api` отдаёт `index.html` на `/`. ### T-E2-004 — Подписки ЮKassa recurring **Статус:** todo · **Оценка:** M · **Зависимости:** T-E2-001, T-E2-003 Тарифы solo (1 490 ₽), team (3 990 ₽). Ежемесячное продление/reset. --- ## Этап 3 — B2B API > **Примечание:** в текущей архитектуре B2B API строится **не после веба**, а поверх уже готового `api` и существующей очереди. > Зависимость от T-E2-002 снята. ### T-E3-001 — Миграция БД: `api_keys` и `api_key_requests` **Статус:** done · **Оценка:** M · **Зависимости:** T-E1-002 `migrations/versions/0002_api_keys.py`: таблицы - `api_keys(id, user_id FK, name, key_hash, rate_limit_rps, monthly_quota, monthly_used, resets_at, revoked, created_at, last_used_at)` - `api_key_requests(id, api_key_id FK, document_id FK, created_at)` Накатывается/откатывается чисто; SQLAlchemy-модели в `core/db/models.py`. ### T-E3-002 — Redis-клиент и token-bucket rate limiter **Статус:** done · **Оценка:** M · **Зависимости:** T-E3-001 `core/redis_client.py` (async Redis из `redis_url`), `core/rate_limit.py` (token bucket per `api_key_id`, с `MemoryRateLimiter`-фолбэком для unit-тестов без Redis). Лимит по умолчанию = `B2B_DEFAULT_RATE_LIMIT_RPS` (3), переопределяется `api_keys.rate_limit_rps`. Покрыт `tests/unit/test_rate_limit.py`; 429 при превышении. ### T-E3-003 — `X-API-Key` auth dependency **Статус:** done · **Оценка:** M · **Зависимости:** T-E3-001, T-E3-002 `api/deps.py`: `require_api_key` — проверяет `X-API-Key` по `key_hash` (`core/api_keys.py`), отклоняет revoked, обновляет `last_used_at`, применяет rate-limit. `api/routes/b2b.py`: `POST /api/v1/analyze`, `GET /api/v1/b2b/reports/{document_id}`, `GET /api/v1/b2b/usage`. Покрыт `tests/integration/test_b2b_api.py` (вкл. 401/429). ### T-E3-004 — Управление API-ключами (user JWT auth) **Статус:** done · **Оценка:** S · **Зависимости:** T-E3-001 `api/routes/b2b.py` под user JWT auth: `POST /api/v1/b2b/keys` (plaintext ключ возвращается **только один раз**), `GET /api/v1/b2b/keys`, `POST /api/v1/b2b/keys/{id}/revoke`, `GET /api/v1/b2b/keys/{id}/usage`. Revoke мгновенно отключает аутентификацию. ### T-E3-005 — Интеграционные тесты B2B API **Статус:** done · **Оценка:** M · **Зависимости:** T-E3-003 `tests/integration/test_b2b_api.py`: upload → 202 → poll report; ветки 401/429. Seed API key в `tests/integration/conftest.py`. --- ## Сводка порядка (обновлённая) 1. **E0** — прототип (done, кроме живых замеров T-E0-006). 2. **E1.001–007** — ядро + БД + FastAPI `api` + worker-extract + worker-analyze + bot + Docker/compose (done). 3. **E3** — B2B API (done: миграция, rate-limit, auth, роуты, тесты). 4. **E1.008** — оплата ЮKassa (todo — блокирует первых платящих). 5. **E1.009–010** — деплой/edge (Nginx/certbot) и observability (Prometheus/Grafana/Tempo/Sentry) (todo). 6. **E2** — веб + подписки (todo; после 10–50 платящих). **Ближайшие работы:** E1.008 (оплата), затем E1.009–010 (edge + observability), затем E2 (веб + подписки).