# Контракт-чек LLM-сервис скрининга рисков в договорах по ГК РФ / ГК РБ. Telegram-бот + web-аутентификация (email/password, JWT-пара; пасскеи и магические ссылки — passwordless) + B2B API + биллинг (ЮKassa: подписки, пакеты кредитов, авто-возвраты) сейчас; web-SPA — позже. Pipeline: `PDF/DOCX/RTF/TXT/CSV/изображения (pymupdf / mammoth / tesseract OCR) → текст → чанки → прескрин (гибридная эвристика + LLM: метаданные договора, роутинг) → LLM-анализ (Ollama Cloud или YandexGPT, json-schema + repair-loop) → markdown-отчёт` с цитатами, ссылкой на пункт и дисклеймером «не заменяет юриста». ## Документы - [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) — **единственный источник истины** для production-рефакторинга. Hexagonal-архитектура, RabbitMQ-конвейер, схемы БД, очередь/ретраи/DLQ, конфиг, deploy, observability. - [`docs/DEPLOY.md`](docs/DEPLOY.md) — руководство по развёртыванию: локально, Docker Compose, VPS, seed-token, backup, troubleshooting. - [`docs/BUSINESS_IDEA.md`](docs/BUSINESS_IDEA.md) — идея и бизнес-модель. - [`docs/TICKETS.md`](docs/TICKETS.md) — тикеты реализации со статусами. ## Архитектура (одна строка) Hexagonal (ports & adapters). Ядро `contract_check.core` владеет всем состоянием (Postgres/MinIO/RabbitMQ/LLM/кредиты/биллинг). Семь сервисов собираются из него: ``` Telegram ──► bot (aiogram, HTTP-only) ──HTTP──► api (FastAPI) ──publish──► RabbitMQ владеет: PG, MinIO, │ Redis, кредитами ▼ extract.q ──► worker-extract (pymupdf/mammoth/tesseract, CPU) │ ▼ publish prescreen.q ──► worker-prescreen (гибридные метаданные + роутинг) │ ┌──────────────────────────────────────┤ ▼ deep_analysis manual_review analyze.q ──► worker-analyze │ (LLM, I/O) → Report │ ▼ notify.q ──► worker-notify (SMTP: password reset, magic link и др.) ``` - **api** — единственный писатель для пользовательских мутаций (upload → reserve credit → MinIO → publish `DocumentUploaded`). - **worker-extract** — CPU: достаёт текст (PDF/DOCX/RTF/TXT/CSV, OCR для изображений; детект формата через python-magic), грузит извлечённый текст в MinIO, публикует `PrescreenRequested` (если `PRESCREEN_ENABLED=true`) или сразу `DocumentExtracted` в `analyze.q`. - **worker-prescreen** — гибридное извлечение метаданных договора (Stage 1 эвристика + Stage 2 LLM при низкой уверенности), роутинг: `deep_analysis` → публикует `AnalyzeRequested` в `analyze.q`; `manual_review` — терминальный статус; `auto_approve` — записывает лёгкий `Report`, `status=done`. Пишет в `prescreen_results`. - **worker-analyze** — I/O: LLM-анализ по чек-листу, валидация/repair, сохраняет `Report`, `status=done`. - **worker-notify** — доставка email-уведомлений (восстановление пароля и др.) через SMTP; без `SMTP_HOST` — dev-логгер. - **worker-billing** — таймерный планировщик биллинга (без MQ): продления подписок (счета за 3 дня до конца периода), expiry/past_due-переходы, сверка pending-счетов старше 15 минут через ЮKassa API. - **bot** — адаптер: HTTP-клиент к api, **не импортирует** core.db/s3/llm/mq (граница проверяется тестом `tests/unit/test_bot_boundary.py`). Может работать на отдельном сервере — см. `docs/DEPLOY.md` §14. ## Структура репозитория ``` src/contract_check/ core/ # общий домен (импортируется каждым сервисом) config.py logging.py telemetry.py sentry.py metrics.py errors.py credits.py tokens.py api_keys.py rate_limit.py redis_client.py auth.py auth_refresh.py auth_refresh_key.py passkeys.py db/ models.py session.py enums.py repositories/ mq/ topology.py publisher.py consumer.py messages.py s3/ port.py minio_storage.py llm/ port.py factory.py ollama_cloud.py yandex_gpt.py prescreen.py errors.py billing/ port.py yookassa.py errors.py quota.py fulfillment.py refunds.py extraction/ port.py factory.py formats.py + adapters/ (pdf_pymupdf, docx_mammoth, rtf_striprtf, txt_chardet, ocr_tesseract) analysis/ extractor.py chunker.py checklist.py report_schema.py ocr.py analyzer.py notifications/ transport.py publisher.py # SMTP + dev-log security/ passwords.py # argon2 api/ # FastAPI-образ app.py deps.py middleware.py services.py __main__.py admin/ # серверный UI по /admin (login, users, счета, возвраты, hold) billing.py users.py auth.py router.py templating.py templates/ billing/ # token-gated HTML pay-страницы (/pay/{id}) routes/ # префикс /api/v1 __init__.py health.py documents.py reports.py me.py metrics.py b2b.py billing.py webhooks.py auth/ # Telegram, email/password, passkeys, magic links __init__.py telegram.py password.py passkeys.py magic_links.py support.py schemas/ # Pydantic-схемы запросов/ответов auth.py b2b.py billing.py common.py documents.py me.py profile.py reports.py worker_extract/ # CPU-образ (pymupdf + mammoth + tesseract) consumer.py handler.py extract_document.py __main__.py worker_prescreen/ # прескрин-образ (гибрид: эвристика + LLM) consumer.py handler.py router.py config.py extractor.py extractor_heuristic.py extractor_llm.py extractor_hybrid.py worker_analyze/ # I/O-образ (LLM provider) consumer.py handler.py __main__.py worker_notify/ # email-уведомления (aiosmtplib) consumer.py handler.py __main__.py worker_billing/ # планировщик биллинга (продления, expiry, сверка) scheduler.py __main__.py bot/ # aiogram-адаптер (самый «тощий» образ: только core.logging) client.py config.py handlers.py rate_limit.py __main__.py docs/ # документация проекта (README остаётся в корне) srv/ # Dockerfile-ы (один на сервис, deps заточены) api/ worker-extract/ worker-prescreen/ worker-analyze/ worker-notify/ worker-billing/ bot/ migrations/ # alembic (async): 0001_initial … 0011_user_profiles_billing tests/ conftest.py unit/ chunker, extractor, extraction_factory/adapters, llm_ollama_cloud, llm_yandex_gpt, llm_prescreen_extraction, prescreen_extractor(+heuristic/ hybrid/llm), prescreen_router, credits, messages, rate_limit, checklist_report, auth, web_auth, passkeys, extract_handler, bot_client, bot_boundary, refunds, worker_billing integration/ upload_pipeline, extract_worker, prescreen_worker, analyze_worker, b2b_api, credits_db, auth_flow, passkeys_magic_link, admin_panel, profile_api, b2b_profile, billing_checkout, webhook_yookassa, pay_page, subscription_purchase, quota_reservation, refunds, admin_billing Makefile # повседневные команды (make help) docker-compose.yml # default = инфра; --profile services = стек; --profile edge = nginx+certbot pyproject.toml # hatchling + PEP 735 dependency-groups (db/mq/s3/obs/api/extract/prescreen/analyze/notify/billing/bot/dev) .env.example # полный список env (см. docs/ARCHITECTURE.md §11) ``` ## Быстрый старт ### Локально (разработка) ```bash uv sync --group dev # все группы для локальной разработки cp .env.example .env # впишите OLLAMA_* / YANDEXGPT_* / JWT_SECRET / BOT_TOKEN docker compose up -d # только инфра (postgres/redis/rabbitmq/minio) uv run alembic upgrade head # миграции uv run python -m contract_check.api # api на :8000 uv run python -m contract_check.worker_extract # воркер экстракции uv run python -m contract_check.worker_prescreen # воркер прескрина uv run python -m contract_check.worker_analyze # воркер анализа uv run python -m contract_check.worker_notify # воркер уведомлений uv run python -m contract_check.worker_billing # планировщик биллинга uv run python -m contract_check.bot # Telegram-бот ``` ### Через Docker Compose / Make ```bash cp .env.example .env # заполнить секреты (DB, Rabbit, MinIO, LLM, BOT_TOKEN, JWT_SECRET, ...) docker compose up -d # только инфра с healthchecks docker compose --profile services up -d --build # + api, worker-extract, worker-prescreen, worker-analyze, worker-notify, worker-billing (без бота) docker compose --profile bot up -d --build # + bot (можно запускать отдельно или на другом хосте) ``` Или через `make`: `make dev` (install + infra + migrate + api + workers + bot), `make help` — полный список целей (lint, typecheck, test, seed-token, jwt-token, admin-promote, логи/шеллы сервисов и т.п.). Профили: - `services` — api + worker-ы (без бота). - `bot` — Telegram-бот; можно поднять на этом же хосте или на отдельном сервере (`docs/DEPLOY.md` §14). - `edge` — Nginx + certbot (`deploy/nginx/`, `docs/DEPLOY.md` §13). - `obs` / `observer` — observability (Grafana/Loki/Prometheus или OpenObserve + OTel collector). Порты на хосте (смещены, чтобы не конфликтовать): Postgres `15432`, Redis `17379`, RabbitMQ AMQP `5672` / UI `15672`, MinIO `9000` / console `9001`, api `8000` / metrics `9100`, worker metrics: extract `9101`, analyze `9102`, notify `9103`, prescreen `9104`, billing `9105`, edge `80`/`443`. ## API (кратко) JSON-роуты под `/api/v1`; серверный admin UI — по `/admin/*` (FastAPI + Jinja2 + HTMX). Auth зависит от роута: - **Пользовательские роуты** (bot / web / Mini App) — `Authorization: Bearer `. Telegram-JWT выдаётся через `/api/v1/auth/telegram/*`; web — JWT-пара (access + refresh, refresh отзывается через Redis); passwordless (пасскей / магическая ссылка) — одиночный access JWT. - **Адаптер-level** (только `/api/v1/auth/telegram/bot`) — `Authorization: Bearer `. - **B2B** — `X-API-Key`. Управление B2B-ключами требует пользовательский JWT. - **Health/metrics** — без auth. - **/admin** — HttpOnly cookie с user JWT + `users.role = admin` (или `ADMIN_REQUIRED_ROLE`). | Метод | Путь | Auth | Назначение | |---|---|---|---| | GET | `/healthz`, `/readyz`, `/metrics` | — | liveness / readiness / Prometheus | | POST | `/api/v1/auth/telegram/bot` | service token | бот меняет verified `telegram_id` на JWT | | POST | `/api/v1/auth/telegram/web` | — | Telegram Login Widget → JWT | | POST | `/api/v1/auth/telegram/miniapp` | — | Mini App `initData` → JWT | | POST | `/api/v1/auth/register` | — | регистрация email/password → JWT-пара | | POST | `/api/v1/auth/login` | — | вход email/password → JWT-пара | | POST | `/api/v1/auth/logout` | — | отзыв refresh-токена | | POST | `/api/v1/auth/forgot-password` | — | reset-токен (хранится хэшем) + уведомление через notify.q | | POST | `/api/v1/auth/reset-password` | — | задать новый пароль, отозвать все refresh | | POST | `/api/v1/auth/passkeys/register/start`, `.../finish` | user JWT | регистрация пасскея (WebAuthn, challenge в Redis) | | POST | `/api/v1/auth/passkeys/authenticate/start`, `.../finish` | — | вход по пасскею (discoverable) → access JWT | | GET/DEL | `/api/v1/auth/passkeys`, `/api/v1/auth/passkeys/{id}` | user JWT | список / удаление пасскеев | | POST | `/api/v1/auth/magic-link/request` | — | одноразовая ссылка входа на email (через notify.q) | | POST | `/api/v1/auth/magic-link/verify` | — | обмен токена из письма на access JWT | | GET | `/api/v1/auth/me`, `/api/v1/auth/me/permissions` | user JWT | introspect JWT / права | | POST | `/api/v1/documents` | user JWT | multipart upload → reserve credit → MinIO → publish → `202 {document_id, correlation_id}` | | GET | `/api/v1/reports/{document_id}` | user JWT | поллинг: `{status, stage}` или готовый `200 {markdown, findings, ...}` | | GET | `/api/v1/me` | user JWT | профиль + `credits_left` | | GET/PATCH | `/api/v1/me/profile` | user JWT | пассивные настройки профиля (язык, TZ, уведомления, дашборд) | | GET | `/api/v1/me/overview` | user JWT | дашборд: документы, кредиты, план/квота, `billing_hold` | | GET | `/api/v1/me/documents` | user JWT | список документов пользователя | | POST | `/api/v1/me/telegram`, `/api/v1/me/password` | user JWT | привязка Telegram / смена пароля | | GET | `/api/v1/billing/plans` | — | каталог тарифов + цена за документ | | POST | `/api/v1/billing/checkout` | user JWT | топ-ап кредитов или покупка подписки → `201 {invoice_id, confirmation_url}` | | GET | `/api/v1/billing/invoices`, `/api/v1/billing/invoices/{id}` | user JWT | история / свой счёт | | GET | `/api/v1/billing/subscription` | user JWT | текущая подписка (active/past_due) | | POST | `/api/v1/billing/subscriptions/autorenew` | user JWT | вкл/выкл автопродление (по умолчанию OFF) | | POST | `/api/v1/billing/refund` | user JWT | авто-возврат по правилу D10; `409` если не возвращаем | | POST | `/api/v1/webhooks/yookassa` | Basic (shopId:secret) | уведомления ЮKassa; платёж перезапрашивается через REST | | GET | `/pay/{invoice_id}?token=…` | подписанный JWT | HTML-страница статуса счёта после оплаты | | GET/PUT | `/api/v1/b2b/profile` | `X-API-Key` | настройки профиля партнёра (без биллинга) | | POST | `/api/v1/analyze` | `X-API-Key` | B2B: анализ документа | | GET | `/api/v1/b2b/reports/{id}`, `/api/v1/b2b/usage` | `X-API-Key` | B2B: отчёт / usage | | POST/GET | `/api/v1/b2b/keys`, `/api/v1/b2b/keys/{id}/revoke`, `.../usage` | user JWT | управление B2B-ключами | | GET/POST | `/admin/*` | admin cookie | login/logout + пользователи (list, create, edit, ban, role, credits, verify-telegram), счета (`/admin/invoices`), возвраты, снятие billing-hold, gift-подписки | Биллинг-флаги: `PLANS_ENABLED` (квоты подписок; `false` = только кредиты), `YOOKASSA_ENABLED` (платежи; `false` → мутации биллинга `503`, каталог читается). Детали: `docs/ARCHITECTURE.md §8a`, настройка ЮKassa — `docs/DEPLOY.md §4a`. Полная спецификация роутов (схемы запросов/ответов, коды ошибок) — [`src/contract_check/api/routes/README.md`](src/contract_check/api/routes/README.md); высокоуровневая поверхность — `docs/ARCHITECTURE.md §15` (актуализируется). ## Проверки (DoD) ```bash uv run ruff check src tests && uv run ruff format --check src tests # lint (или make lint) uv run ty check src # типы (или make typecheck) uv run pytest -m "not integration" -q # unit, быстро uv run pytest -m integration -q # интеграционные (нужны контейнеры) ```